mirror of
https://github.com/bringout/oca-ocb-mail.git
synced 2026-04-18 11:42:02 +02:00
Initial commit: Mail packages
This commit is contained in:
commit
4e53507711
1948 changed files with 751201 additions and 0 deletions
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_blacklist
|
||||
from . import test_blacklist_behavior
|
||||
from . import test_blacklist_mixin
|
||||
from . import test_link_tracker
|
||||
from . import test_link_tracker_sms
|
||||
from . import test_mailing
|
||||
from . import test_mailing_server
|
||||
from . import test_mailing_sms
|
||||
from . import test_mailing_statistics
|
||||
from . import test_mailing_statistics_sms
|
||||
from . import test_mailing_test
|
||||
from . import test_performance
|
||||
from . import test_utm
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
|
||||
from odoo.addons.mass_mailing_sms.tests.common import MassSMSCommon
|
||||
from odoo.addons.test_mail_sms.tests.common import TestSMSCommon
|
||||
|
||||
|
||||
class TestMassMailCommon(MassSMSCommon, TestSMSCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailCommon, cls).setUpClass()
|
||||
|
||||
cls.test_alias = cls.env['mail.alias'].create({
|
||||
'alias_name': 'test.alias',
|
||||
'alias_user_id': False,
|
||||
'alias_model_id': cls.env['ir.model']._get('mailing.test.simple').id,
|
||||
'alias_contact': 'everyone'
|
||||
})
|
||||
|
||||
# enforce last update by user_marketing to match _process_mass_mailing_queue
|
||||
# taking last writer as user running a batch
|
||||
cls.mailing_bl = cls.env['mailing.mailing'].with_user(cls.user_marketing).create({
|
||||
'name': 'SourceName',
|
||||
'subject': 'MailingSubject',
|
||||
# `+ ""` is for insuring that _prepend_preview rule out that case
|
||||
'preview': 'Hi {{ object.name + "" }} :)',
|
||||
'body_html': """<div><p>Hello <t t-out="object.name"/></p>,
|
||||
<t t-set="url" t-value="'www.odoo.com'"/>
|
||||
<t t-set="httpurl" t-value="'https://www.odoo.eu'"/>f
|
||||
<span>Website0: <a id="url0" t-attf-href="https://www.odoo.tz/my/{{object.name}}">https://www.odoo.tz/my/<t t-out="object.name"/></a></span>
|
||||
<span>Website1: <a id="url1" href="https://www.odoo.be">https://www.odoo.be</a></span>
|
||||
<span>Website2: <a id="url2" t-attf-href="https://{{url}}">https://<t t-out="url"/></a></span>
|
||||
<span>Website3: <a id="url3" t-att-href="httpurl"><t t-out="httpurl"/></a></span>
|
||||
<span>External1: <a id="url4" href="https://www.example.com/foo/bar?baz=qux">Youpie</a></span>
|
||||
<span>Internal1: <a id="url5" href="/event/dummy-event-0">Internal link</a></span>
|
||||
<span>Internal2: <a id="url6" href="/view"/>View link</a></span>
|
||||
<span>Email: <a id="url7" href="mailto:test@odoo.com">test@odoo.com</a></span>
|
||||
<p>Stop spam ? <a id="url8" role="button" href="/unsubscribe_from_list">Ok</a></p>
|
||||
</div>""",
|
||||
'mailing_type': 'mail',
|
||||
'mailing_model_id': cls.env['ir.model']._get('mailing.test.blacklist').id,
|
||||
'reply_to_mode': 'update',
|
||||
})
|
||||
|
||||
cls.mailing_sms = cls.env['mailing.mailing'].with_user(cls.user_marketing).create({
|
||||
'name': 'XMas SMS',
|
||||
'subject': 'Xmas SMS for {object.name}',
|
||||
'mailing_model_id': cls.env['ir.model']._get('mail.test.sms').id,
|
||||
'mailing_type': 'sms',
|
||||
'mailing_domain': '%s' % repr([('name', 'ilike', 'MassSMSTest')]),
|
||||
'body_plaintext': 'Dear {{object.display_name}} this is a mass SMS with two links http://www.odoo.com/smstest and http://www.odoo.com/smstest/{{object.id}}',
|
||||
'sms_force_send': True,
|
||||
'sms_allow_unsubscribe': True,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def _create_test_blacklist_records(cls, model='mailing.test.blacklist', count=1):
|
||||
""" Deprecated, remove in 14.4 """
|
||||
return cls.__create_mailing_test_records(model=model, count=count)
|
||||
|
||||
@classmethod
|
||||
def _create_mailing_sms_test_records(cls, model='mail.test.sms', partners=None, count=1):
|
||||
""" Helper to create data. Currently simple, to be improved. """
|
||||
Model = cls.env[model]
|
||||
phone_field = 'phone_nbr' if 'phone_nbr' in Model else 'phone'
|
||||
partner_field = 'customer_id' if 'customer_id' in Model else 'partner_id'
|
||||
|
||||
vals_list = []
|
||||
for idx in range(count):
|
||||
vals = {
|
||||
'name': 'MassSMSTestRecord_%02d' % idx,
|
||||
phone_field: '045600%02d%02d' % (idx, idx)
|
||||
}
|
||||
if partners:
|
||||
vals[partner_field] = partners[idx % len(partners)]
|
||||
|
||||
vals_list.append(vals)
|
||||
|
||||
return cls.env[model].create(vals_list)
|
||||
|
||||
@classmethod
|
||||
def _create_mailing_test_records(cls, model='mailing.test.blacklist', partners=None, count=1):
|
||||
""" Helper to create data. Currently simple, to be improved. """
|
||||
Model = cls.env[model]
|
||||
email_field = 'email' if 'email' in Model else 'email_from'
|
||||
partner_field = 'customer_id' if 'customer_id' in Model else 'partner_id'
|
||||
|
||||
vals_list = []
|
||||
for x in range(0, count):
|
||||
vals = {
|
||||
'name': 'TestRecord_%02d' % x,
|
||||
email_field: '"TestCustomer %02d" <test.record.%02d@test.example.com>' % (x, x),
|
||||
}
|
||||
if partners:
|
||||
vals[partner_field] = partners[x % len(partners)]
|
||||
|
||||
vals_list.append(vals)
|
||||
|
||||
return cls.env[model].create(vals_list)
|
||||
|
||||
|
||||
class TestMassSMSCommon(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassSMSCommon, cls).setUpClass()
|
||||
cls._test_body = 'Mass SMS in your face'
|
||||
|
||||
records = cls.env['mail.test.sms']
|
||||
partners = cls.env['res.partner']
|
||||
country_be_id = cls.env.ref('base.be').id
|
||||
_country_us_id = cls.env.ref('base.us').id
|
||||
|
||||
for x in range(10):
|
||||
partners += cls.env['res.partner'].with_context(**cls._test_context).create({
|
||||
'name': 'Partner_%s' % (x),
|
||||
'email': '_test_partner_%s@example.com' % (x),
|
||||
'country_id': country_be_id,
|
||||
'mobile': '045600%s%s99' % (x, x)
|
||||
})
|
||||
records += cls.env['mail.test.sms'].with_context(**cls._test_context).create({
|
||||
'name': 'MassSMSTest_%s' % (x),
|
||||
'customer_id': partners[x].id,
|
||||
'phone_nbr': '045600%s%s44' % (x, x)
|
||||
})
|
||||
cls.records = cls._reset_mail_context(records)
|
||||
cls.records_numbers = [phone_validation.phone_format(r.phone_nbr, 'BE', '32', force_format='E164') for r in cls.records]
|
||||
cls.partners = partners
|
||||
|
||||
cls.sms_template = cls.env['sms.template'].create({
|
||||
'name': 'Test Template',
|
||||
'model_id': cls.env['ir.model']._get('mail.test.sms').id,
|
||||
'body': 'Dear {{ object.display_name }} this is a mass SMS.',
|
||||
})
|
||||
|
||||
cls.partner_numbers = [
|
||||
phone_validation.phone_format(partner.mobile, partner.country_id.code, partner.country_id.phone_code, force_format='E164')
|
||||
for partner in partners
|
||||
]
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import users
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
|
||||
class TestBLAccessRights(common.TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestBLAccessRights, cls).setUpClass()
|
||||
cls._create_portal_user()
|
||||
|
||||
cls.bl_rec = cls.env['mail.blacklist'].create([
|
||||
{'email': 'Not A Stark <john.snow@example.com>'},
|
||||
])
|
||||
cls.bl_previous = cls.env['mail.blacklist'].search([])
|
||||
|
||||
@users('employee')
|
||||
def test_bl_crud_employee(self):
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.blacklist'].create([{'email': 'Arya.Stark@example.com'}])
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).read([])
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).write({'email': 'jaimie.lannister@example.com'})
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).unlink()
|
||||
|
||||
@users('portal_test')
|
||||
def test_bl_crud_portal(self):
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['mail.blacklist'].create([{'email': 'Arya.Stark@example.com'}])
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).read([])
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).write({'email': 'jaimie.lannister@example.com'})
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.bl_rec.with_user(self.env.user).unlink()
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_crud_marketing(self):
|
||||
self.env['mail.blacklist'].create([{'email': 'Arya.Stark@example.com'}])
|
||||
|
||||
read_res = self.bl_rec.with_user(self.env.user).read([])
|
||||
self.assertEqual(read_res[0]['id'], self.bl_rec.id)
|
||||
|
||||
self.bl_rec.with_user(self.env.user).write({'email': 'jaimie.lannister@example.com'})
|
||||
self.assertEqual(self.bl_rec.email, 'jaimie.lannister@example.com')
|
||||
|
||||
self.bl_rec.with_user(self.env.user).unlink()
|
||||
|
||||
|
||||
class TestBLConsistency(common.TestMassMailCommon):
|
||||
_base_list = ['Arya.Stark@example.com', 'ned.stark@example.com']
|
||||
|
||||
def setUp(self):
|
||||
super(TestBLConsistency, self).setUp()
|
||||
self.bl_rec = self.env['mail.blacklist'].create([
|
||||
{'email': 'Not A Stark <john.snow@example.com>'},
|
||||
])
|
||||
|
||||
self.bl_previous = self.env['mail.blacklist'].search([])
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_check_case_add(self):
|
||||
""" Test emails case when adding through _add """
|
||||
bl_sudo = self.env['mail.blacklist'].sudo()
|
||||
existing = bl_sudo.create({
|
||||
'email': 'arya.stark@example.com',
|
||||
'active': False,
|
||||
})
|
||||
|
||||
added = self.env['mail.blacklist']._add('Arya.Stark@EXAMPLE.com')
|
||||
self.assertEqual(existing, added)
|
||||
self.assertTrue(existing.active)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_check_case_remove(self):
|
||||
""" Test emails case when deactivating through _remove """
|
||||
bl_sudo = self.env['mail.blacklist'].sudo()
|
||||
existing = bl_sudo.create({
|
||||
'email': 'arya.stark@example.com',
|
||||
'active': True,
|
||||
})
|
||||
|
||||
added = self.env['mail.blacklist']._remove('Arya.Stark@EXAMPLE.com')
|
||||
self.assertEqual(existing, added)
|
||||
self.assertFalse(existing.active)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_create_duplicate(self):
|
||||
""" Test emails are inserted only once if duplicated """
|
||||
bl_sudo = self.env['mail.blacklist'].sudo()
|
||||
self.env['mail.blacklist'].create([
|
||||
{'email': self._base_list[0]},
|
||||
{'email': self._base_list[1]},
|
||||
{'email': 'Another Ned Stark <%s>' % self._base_list[1]},
|
||||
])
|
||||
|
||||
new_bl = bl_sudo.search([('id', 'not in', self.bl_previous.ids)])
|
||||
|
||||
self.assertEqual(len(new_bl), 2)
|
||||
self.assertEqual(
|
||||
set(v.lower() for v in self._base_list),
|
||||
set(v.lower() for v in new_bl.mapped('email'))
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_create_parsing(self):
|
||||
""" Test email is correctly extracted from given entries """
|
||||
bl_sudo = self.env['mail.blacklist'].sudo()
|
||||
self.env['mail.blacklist'].create([
|
||||
{'email': self._base_list[0]},
|
||||
{'email': self._base_list[1]},
|
||||
{'email': 'Not Ned Stark <jaimie.lannister@example.com>'},
|
||||
])
|
||||
|
||||
new_bl = bl_sudo.search([('id', 'not in', self.bl_previous.ids)])
|
||||
|
||||
self.assertEqual(len(new_bl), 3)
|
||||
self.assertEqual(
|
||||
set(v.lower() for v in self._base_list + ['jaimie.lannister@example.com']),
|
||||
set(v.lower() for v in new_bl.mapped('email'))
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_search_exact(self):
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', 'john.snow@example.com')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_search_parsing(self):
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', 'Not A Stark <john.snow@example.com>')])
|
||||
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', '"John J. Snow" <john.snow@example.com>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', 'Aegon? <john.snow@example.com>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', '"John; \"You know Nothing\" Snow" <john.snow@example.com>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_search_case(self):
|
||||
search_res = self.env['mail.blacklist'].search([('email', '=', 'john.SNOW@example.COM>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_bl_search_partial(self):
|
||||
search_res = self.env['mail.blacklist'].search([('email', 'ilike', 'John')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
search_res = self.env['mail.blacklist'].search([('email', 'ilike', 'n.SNOW@example.cO>')])
|
||||
self.assertEqual(search_res, self.bl_rec)
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
|
||||
from freezegun import freeze_time
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.mass_mailing.models.mail_thread import BLACKLIST_MAX_BOUNCED_LIMIT
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.sql_db import Cursor
|
||||
|
||||
|
||||
@tagged('mail_blacklist')
|
||||
class TestAutoBlacklist(common.TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestAutoBlacklist, cls).setUpClass()
|
||||
cls.target_rec = cls._create_mailing_test_records()[0]
|
||||
cls.mailing_bl.write({'mailing_domain': [('id', 'in', cls.target_rec.ids)]})
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_bounce_w_auto_bl(self):
|
||||
self._test_mailing_bounce_w_auto_bl(None)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_bounce_w_auto_bl_partner(self):
|
||||
bounced_partner = self.env['res.partner'].sudo().create({
|
||||
'name': 'Bounced Partner',
|
||||
'email': self.target_rec.email_from,
|
||||
'message_bounce': BLACKLIST_MAX_BOUNCED_LIMIT,
|
||||
})
|
||||
self._test_mailing_bounce_w_auto_bl({'bounced_partner': bounced_partner})
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_bounce_w_auto_bl_partner_duplicates(self):
|
||||
bounced_partners = self.env['res.partner'].sudo().create({
|
||||
'name': 'Bounced Partner1',
|
||||
'email': self.target_rec.email_from,
|
||||
'message_bounce': BLACKLIST_MAX_BOUNCED_LIMIT,
|
||||
}) | self.env['res.partner'].sudo().create({
|
||||
'name': 'Bounced Partner2',
|
||||
'email': self.target_rec.email_from,
|
||||
'message_bounce': BLACKLIST_MAX_BOUNCED_LIMIT,
|
||||
})
|
||||
self._test_mailing_bounce_w_auto_bl({'bounced_partner': bounced_partners})
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def _test_mailing_bounce_w_auto_bl(self, bounce_base_values):
|
||||
mailing = self.mailing_bl.with_env(self.env)
|
||||
target = self.target_rec.with_env(self.env)
|
||||
|
||||
# create bounced history of 4 statistics
|
||||
traces = self.env['mailing.trace']
|
||||
for idx in range(4):
|
||||
new_mailing = mailing.copy()
|
||||
new_dt = datetime.datetime.now() - datetime.timedelta(weeks=idx+2)
|
||||
# Cursor.now() uses transaction's timestamp and not datetime lib -> freeze_time
|
||||
# is not sufficient
|
||||
with freeze_time(new_dt), patch.object(Cursor, 'now', lambda *args, **kwargs: new_dt):
|
||||
traces += self._create_bounce_trace(new_mailing, target, dt=datetime.datetime.now() - datetime.timedelta(weeks=idx+2))
|
||||
self.gateway_mail_bounce(new_mailing, target, bounce_base_values)
|
||||
|
||||
# mass mail record: ok, not blacklisted yet
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com'}],
|
||||
mailing, target,
|
||||
check_mail=True
|
||||
)
|
||||
|
||||
# call bounced
|
||||
self.gateway_mail_bounce(mailing, target, bounce_base_values)
|
||||
|
||||
# check blacklist
|
||||
blacklist_record = self.env['mail.blacklist'].sudo().search([('email', '=', target.email_normalized)])
|
||||
self.assertEqual(len(blacklist_record), 1)
|
||||
self.assertTrue(target.is_blacklisted)
|
||||
|
||||
# mass mail record: ko, blacklisted
|
||||
new_mailing = mailing.copy({'mailing_domain': [('id', 'in', target.ids)]})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
new_mailing.action_send_mail()
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'}],
|
||||
new_mailing, target, check_mail=True
|
||||
)
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.models.mailing_models import MailingBLacklist
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
class TestBLMixin(common.TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestBLMixin, cls).setUpClass()
|
||||
|
||||
cls.env['mail.blacklist'].create([{
|
||||
'email': 'Arya.Stark@example.com',
|
||||
'active': True,
|
||||
}, {
|
||||
'email': 'Sansa.Stark@example.com',
|
||||
'active': False,
|
||||
}])
|
||||
|
||||
@users('employee')
|
||||
def test_bl_mixin_primary_field_consistency(self):
|
||||
MailingBLacklist._primary_email = 'not_a_field'
|
||||
with self.assertRaises(UserError):
|
||||
self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', False)])
|
||||
|
||||
MailingBLacklist._primary_email = ['not_a_str']
|
||||
with self.assertRaises(UserError):
|
||||
self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', False)])
|
||||
|
||||
MailingBLacklist._primary_email = 'email_from'
|
||||
self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', False)])
|
||||
|
||||
@users('employee')
|
||||
def test_bl_mixin_is_blacklisted(self):
|
||||
""" Test is_blacklisted field computation """
|
||||
record = self.env['mailing.test.blacklist'].create({'email_from': 'arya.stark@example.com'})
|
||||
self.assertTrue(record.is_blacklisted)
|
||||
|
||||
record = self.env['mailing.test.blacklist'].create({'email_from': 'not.arya.stark@example.com'})
|
||||
self.assertFalse(record.is_blacklisted)
|
||||
|
||||
@users('employee')
|
||||
def test_bl_mixin_search_blacklisted(self):
|
||||
""" Test is_blacklisted field search implementation """
|
||||
record1 = self.env['mailing.test.blacklist'].create({'email_from': 'arya.stark@example.com'})
|
||||
record2 = self.env['mailing.test.blacklist'].create({'email_from': 'not.arya.stark@example.com'})
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', False)])
|
||||
self.assertEqual(search_res, record2)
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '!=', True)])
|
||||
self.assertEqual(search_res, record2)
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', True)])
|
||||
self.assertEqual(search_res, record1)
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '!=', False)])
|
||||
self.assertEqual(search_res, record1)
|
||||
|
||||
@users('employee')
|
||||
def test_bl_mixin_search_blacklisted_format(self):
|
||||
""" Test is_blacklisted field search using email parsing """
|
||||
record1 = self.env['mailing.test.blacklist'].create({'email_from': 'Arya Stark <arya.stark@example.com>'})
|
||||
self.assertTrue(record1.is_blacklisted)
|
||||
|
||||
search_res = self.env['mailing.test.blacklist'].search([('is_blacklisted', '=', True)])
|
||||
self.assertEqual(search_res, record1)
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import users
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
|
||||
|
||||
class TestLinkTracker(common.TestMassMailCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLinkTracker, self).setUp()
|
||||
|
||||
self.link = self.env['link.tracker'].search_or_create({
|
||||
'url': 'https://www.example.com'
|
||||
})
|
||||
|
||||
self.click = self.env['link.tracker.click'].create({
|
||||
'link_id': self.link.id,
|
||||
'ip': '100.00.00.00',
|
||||
'country_id': self.env.ref('base.fr').id,
|
||||
})
|
||||
|
||||
def test_add_link(self):
|
||||
code = self.link.code
|
||||
self.assertEqual(self.link.count, 1)
|
||||
|
||||
# click from a new IP should create a new entry
|
||||
click = self.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='BEL'
|
||||
)
|
||||
self.assertEqual(click.ip, '100.00.00.01')
|
||||
self.assertEqual(click.country_id, self.env.ref('base.be'))
|
||||
self.assertEqual(self.link.count, 2)
|
||||
|
||||
# click from same IP (even another country) does not create a new entry
|
||||
click = self.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='FRA'
|
||||
)
|
||||
self.assertEqual(click, None)
|
||||
self.assertEqual(self.link.count, 2)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_add_link_mail_stat(self):
|
||||
record = self.env['mailing.test.blacklist'].create({})
|
||||
code = self.link.code
|
||||
self.assertEqual(self.link.count, 1)
|
||||
trace = self.env['mailing.trace'].create({
|
||||
'mass_mailing_id': self.mailing_bl.id,
|
||||
'model': record._name,
|
||||
'res_id': record.id,
|
||||
})
|
||||
self.assertEqual(trace.trace_status, 'outgoing')
|
||||
self.assertFalse(trace.links_click_datetime)
|
||||
|
||||
# click from a new IP should create a new entry and update stat when provided
|
||||
click = self.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='BEL',
|
||||
mailing_trace_id=trace.id
|
||||
)
|
||||
self.assertEqual(self.link.count, 2)
|
||||
self.assertEqual(click.mass_mailing_id, self.mailing_bl)
|
||||
self.assertTrue(trace.trace_status, 'open')
|
||||
self.assertTrue(trace.links_click_datetime)
|
||||
self.assertEqual(trace.links_click_ids, click)
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('link_tracker')
|
||||
class TestSMSPost(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSMSPost, cls).setUpClass()
|
||||
cls._test_body = 'VOID CONTENT'
|
||||
|
||||
cls.sms_all = cls.env['sms.sms']
|
||||
for x in range(10):
|
||||
cls.sms_all |= cls.env['sms.sms'].create({
|
||||
'number': '+324560000%s%s' % (x, x),
|
||||
'body': cls._test_body,
|
||||
})
|
||||
|
||||
# tracking info
|
||||
cls.utm_c = cls.env['utm.campaign'].create({
|
||||
'name': 'UTM C',
|
||||
'stage_id': cls.env.ref('utm.default_utm_stage').id,
|
||||
'is_auto_campaign': True,
|
||||
})
|
||||
cls.utm_m = cls.env.ref('mass_mailing_sms.utm_medium_sms')
|
||||
cls.tracker_values = {
|
||||
'campaign_id': cls.utm_c.id,
|
||||
'medium_id': cls.utm_m.id,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestSMSPost, self).setUp()
|
||||
self._web_base_url = 'https://test.odoo.com'
|
||||
self.env['ir.config_parameter'].sudo().set_param('web.base.url', self._web_base_url)
|
||||
|
||||
def test_body_link_shorten(self):
|
||||
link = 'http://www.example.com'
|
||||
self.env['link.tracker'].search([('url', '=', link)]).unlink()
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
|
||||
self.assertNotIn(link, new_body)
|
||||
self.assertLinkShortenedText(new_body, (link, True), {'utm_campaign': self.utm_c.name, 'utm_medium': self.utm_m.name})
|
||||
link = self.env['link.tracker'].search([('url', '=', link)])
|
||||
self.assertIn(link.short_url, new_body)
|
||||
|
||||
link = f'{self._web_base_url}/my/super_page?test[0]=42&toto=áâà#title3'
|
||||
self.env['link.tracker'].search([('url', '=', link)]).unlink()
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
|
||||
self.assertNotIn(link, new_body)
|
||||
self.assertLinkShortenedText(new_body, (link, True), {
|
||||
'utm_campaign': self.utm_c.name,
|
||||
'utm_medium': self.utm_m.name,
|
||||
'test[0]': '42',
|
||||
'toto': 'áâà',
|
||||
})
|
||||
link = self.env['link.tracker'].search([('url', '=', link)])
|
||||
self.assertIn(link.short_url, new_body)
|
||||
# Bugfix: ensure void content convert does not crash
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text(False, self.tracker_values)
|
||||
self.assertFalse(new_body)
|
||||
|
||||
def test_body_link_shorten_wshort(self):
|
||||
link = f'{self._web_base_url}/r/RAOUL'
|
||||
self.env['link.tracker'].search([('url', '=', link)]).unlink()
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
|
||||
self.assertIn(link, new_body)
|
||||
self.assertFalse(self.env['link.tracker'].search([('url', '=', link)]))
|
||||
|
||||
def test_body_link_shorten_wunsubscribe(self):
|
||||
link = f'{self._web_base_url}/sms/3/'
|
||||
self.env['link.tracker'].search([('url', '=', link)]).unlink()
|
||||
new_body = self.env['mail.render.mixin']._shorten_links_text('Welcome to %s !' % link, self.tracker_values)
|
||||
self.assertIn(link, new_body)
|
||||
self.assertFalse(self.env['link.tracker'].search([('url', '=', link)]))
|
||||
|
||||
def test_sms_body_link_shorten_suffix(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'subject': 'Minimal mailing',
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms').id,
|
||||
'mailing_type': 'sms',
|
||||
})
|
||||
|
||||
sms_0 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}',
|
||||
'number': '10',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
sms_1 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}/r/RAOUL',
|
||||
'number': '11',
|
||||
})
|
||||
sms_2 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}/r/RAOUL',
|
||||
'number': '12',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
sms_3 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}/leodagan/r/RAOUL',
|
||||
'number': '13',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
sms_4 = self.env['sms.sms'].create({
|
||||
'body': f'Welcome to {self._web_base_url}/r/RAOUL\nAnd again,\n'
|
||||
f'{self._web_base_url}/r/RAOUL',
|
||||
'number': '14',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
res = (sms_0 | sms_1 | sms_2 | sms_3 | sms_4)._update_body_short_links()
|
||||
self.assertEqual(res[sms_0.id], f'Welcome to {self._web_base_url}')
|
||||
self.assertEqual(res[sms_1.id], f'Welcome to {self._web_base_url}/r/RAOUL')
|
||||
self.assertEqual(res[sms_2.id], f'Welcome to {self._web_base_url}/r/RAOUL/s/%s' % sms_2.id)
|
||||
self.assertEqual(res[sms_3.id], f'Welcome to {self._web_base_url}/leodagan/r/RAOUL')
|
||||
self.assertEqual(
|
||||
res[sms_4.id],
|
||||
f'Welcome to {self._web_base_url}/r/RAOUL/s/{sms_4.id}\nAnd again,\n{self._web_base_url}/r/RAOUL/s/{sms_4.id}')
|
||||
|
|
@ -0,0 +1,590 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger, email_normalize
|
||||
|
||||
|
||||
@tagged('mass_mailing')
|
||||
class TestMassMailing(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailing, cls).setUpClass()
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_gateway_reply(self):
|
||||
customers = self.env['res.partner']
|
||||
for x in range(0, 3):
|
||||
customers |= self.env['res.partner'].create({
|
||||
'name': 'Customer_%02d' % x,
|
||||
'email': '"Customer_%02d" <customer_%02d@test.example.com' % (x, x),
|
||||
})
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestName',
|
||||
'subject': 'TestSubject',
|
||||
'body_html': 'Hello <t t-out="object.name" />',
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
|
||||
'keep_archives': True,
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
'mailing_domain': '%s' % [('id', 'in', customers.ids)],
|
||||
})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, customers[1], use_in_reply_to=False)
|
||||
|
||||
# customer2 looses headers
|
||||
mail_mail = self._find_mail_mail_wrecord(customers[2])
|
||||
self.format_and_process(
|
||||
MAIL_TEMPLATE,
|
||||
mail_mail.email_to,
|
||||
mail_mail.reply_to,
|
||||
subject='Re: %s' % mail_mail.subject,
|
||||
extra='',
|
||||
msg_id='<123456.%s.%d@test.example.com>' % (customers[2]._name, customers[2].id),
|
||||
target_model=customers[2]._name, target_field=customers[2]._rec_name,
|
||||
)
|
||||
mailing.flush_recordset()
|
||||
|
||||
# check traces status
|
||||
traces = self.env['mailing.trace'].search([('model', '=', customers._name), ('res_id', 'in', customers.ids)])
|
||||
self.assertEqual(len(traces), 3)
|
||||
customer0_trace = traces.filtered(lambda t: t.res_id == customers[0].id)
|
||||
self.assertEqual(customer0_trace.trace_status, 'reply')
|
||||
customer1_trace = traces.filtered(lambda t: t.res_id == customers[1].id)
|
||||
self.assertEqual(customer1_trace.trace_status, 'reply')
|
||||
customer2_trace = traces.filtered(lambda t: t.res_id == customers[2].id)
|
||||
self.assertEqual(customer2_trace.trace_status, 'sent')
|
||||
|
||||
# check mailing statistics
|
||||
self.assertEqual(mailing.sent, 3)
|
||||
self.assertEqual(mailing.delivered, 3)
|
||||
self.assertEqual(mailing.opened, 2)
|
||||
self.assertEqual(mailing.replied, 2)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_gateway_update(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.optout', count=5)
|
||||
self.assertEqual(len(recipients), 5)
|
||||
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout'),
|
||||
'mailing_domain': [('id', 'in', recipients.ids)]
|
||||
})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': record.email_normalized}
|
||||
for record in recipients],
|
||||
mailing, recipients,
|
||||
mail_links_info=[[
|
||||
('url0', 'https://www.odoo.tz/my/%s' % record.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, {'baz': 'qux'}),
|
||||
('url5', '%s/event/dummy-event-0' % mailing.get_base_url(), True, {}),
|
||||
# view is not shortened and parsed at sending
|
||||
('url6', '%s/view' % mailing.get_base_url(), False, {}),
|
||||
('url7', 'mailto:test@odoo.com', False, {}),
|
||||
# unsubscribe is not shortened and parsed at sending
|
||||
('url8', '%s/unsubscribe_from_list' % mailing.get_base_url(), False, {}),
|
||||
] for record in recipients],
|
||||
check_mail=True
|
||||
)
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5)
|
||||
|
||||
# simulate a click
|
||||
self.gateway_mail_click(mailing, recipients[0], 'https://www.odoo.be')
|
||||
mailing.invalidate_recordset()
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, clicked=1)
|
||||
|
||||
# simulate a bounce
|
||||
self.assertEqual(recipients[1].message_bounce, 0)
|
||||
self.gateway_mail_bounce(mailing, recipients[1])
|
||||
mailing.invalidate_recordset()
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=4, sent=5, opened=1, clicked=1, bounced=1)
|
||||
self.assertEqual(recipients[1].message_bounce, 1)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_recipients(self):
|
||||
""" Test recipient-specific computation, with email, formatting,
|
||||
multi-emails, ... to test corner cases. Blacklist mixin impact is
|
||||
tested. """
|
||||
(customer_mult, customer_fmt, customer_unic,
|
||||
customer_case, customer_weird, customer_weird_2
|
||||
) = self.env['res.partner'].create([
|
||||
{
|
||||
'email': 'customer.multi.1@example.com, "Test Multi 2" <customer.multi.2@example.com>',
|
||||
'name': 'MultiEMail',
|
||||
}, {
|
||||
'email': '"Formatted Customer" <test.customer.format@example.com>',
|
||||
'name': 'FormattedEmail',
|
||||
}, {
|
||||
'email': '"Unicode Customer" <test.customer.😊@example.com>',
|
||||
'name': 'UnicodeEmail',
|
||||
}, {
|
||||
'email': 'TEST.CUSTOMER.CASE@EXAMPLE.COM',
|
||||
'name': 'CaseEmail',
|
||||
}, {
|
||||
'email': 'test.customer.weird@example.com Weird Format',
|
||||
'name': 'WeirdFormatEmail',
|
||||
}, {
|
||||
'email': 'Weird Format2 test.customer.weird.2@example.com',
|
||||
'name': 'WeirdFormatEmail2',
|
||||
}
|
||||
])
|
||||
|
||||
# check difference of email management between a classic model and a model
|
||||
# with an 'email_normalized' field (blacklist mixin)
|
||||
for dst_model in ['mailing.test.customer', 'mailing.test.blacklist']:
|
||||
with self.subTest(dst_model=dst_model):
|
||||
(record_p_mult, record_p_fmt, record_p_unic,
|
||||
record_p_case, record_p_weird, record_p_weird_2,
|
||||
record_mult, record_fmt, record_unic,
|
||||
record_case, recod_weird, record_weird_2
|
||||
) = self.env[dst_model].create([
|
||||
{
|
||||
'customer_id': customer_mult.id,
|
||||
}, {
|
||||
'customer_id': customer_fmt.id,
|
||||
}, {
|
||||
'customer_id': customer_unic.id,
|
||||
}, {
|
||||
'customer_id': customer_case.id,
|
||||
}, {
|
||||
'customer_id': customer_weird.id,
|
||||
}, {
|
||||
'customer_id': customer_weird_2.id,
|
||||
}, {
|
||||
'email_from': 'record.multi.1@example.com, "Record Multi 2" <record.multi.2@example.com>',
|
||||
}, {
|
||||
'email_from': '"Formatted Record" <record.format@example.com>',
|
||||
}, {
|
||||
'email_from': '"Unicode Record" <record.😊@example.com>',
|
||||
}, {
|
||||
'email_from': 'TEST.RECORD.CASE@EXAMPLE.COM',
|
||||
}, {
|
||||
'email_from': 'test.record.weird@example.com Weird Format',
|
||||
}, {
|
||||
'email_from': 'Weird Format2 test.record.weird.2@example.com',
|
||||
}
|
||||
])
|
||||
test_records = (
|
||||
record_p_mult + record_p_fmt + record_p_unic +
|
||||
record_p_case + record_p_weird + record_p_weird_2 +
|
||||
record_mult + record_fmt + record_unic +
|
||||
record_case + recod_weird + record_weird_2
|
||||
)
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'body_html': """<div><p>Hello ${object.name}</p>""",
|
||||
'mailing_domain': [('id', 'in', test_records.ids)],
|
||||
'mailing_model_id': self.env['ir.model']._get_id(dst_model),
|
||||
'mailing_type': 'mail',
|
||||
'name': 'SourceName',
|
||||
'preview': 'Hi ${object.name} :)',
|
||||
'reply_to_mode': 'update',
|
||||
'subject': 'MailingSubject',
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
# Difference in email, email_to_recipients and email_to_mail
|
||||
# -> email: trace email: normalized, to ease its management, mainly technical
|
||||
# -> email_to_mail: mail.mail email: email_to stored in outgoing mail.mail (can be multi)
|
||||
# -> email_to_recipients: email_to for outgoing emails, list means several recipients
|
||||
self.assertMailTraces(
|
||||
[
|
||||
{'email': 'customer.multi.1@example.com, "Test Multi 2" <customer.multi.2@example.com>',
|
||||
'email_to_recipients': [[f'"{customer_mult.name}" <customer.multi.1@example.com>', f'"{customer_mult.name}" <customer.multi.2@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_mult,
|
||||
'trace_status': 'sent'},
|
||||
{'email': '"Formatted Customer" <test.customer.format@example.com>',
|
||||
# mail to avoids double encapsulation
|
||||
'email_to_recipients': [[f'"{customer_fmt.name}" <test.customer.format@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_fmt,
|
||||
'trace_status': 'sent'},
|
||||
{'email': '"Unicode Customer" <test.customer.😊@example.com>',
|
||||
# mail to avoids double encapsulation
|
||||
'email_to_recipients': [[f'"{customer_unic.name}" <test.customer.😊@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_unic,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'TEST.CUSTOMER.CASE@EXAMPLE.COM',
|
||||
'email_to_recipients': [[f'"{customer_case.name}" <test.customer.case@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_case,
|
||||
'trace_status': 'sent'}, # lower cased
|
||||
{'email': 'test.customer.weird@example.com Weird Format',
|
||||
'email_to_recipients': [[f'"{customer_weird.name}" <test.customer.weird@example.comweirdformat>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_weird,
|
||||
'trace_status': 'sent'}, # concatenates everything after domain
|
||||
{'email': 'Weird Format2 test.customer.weird.2@example.com',
|
||||
'email_to_recipients': [[f'"{customer_weird_2.name}" <test.customer.weird.2@example.com>']],
|
||||
'failure_type': False,
|
||||
'partner': customer_weird_2,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'record.multi.1@example.com',
|
||||
'email_to_mail': 'record.multi.1@example.com,record.multi.2@example.com',
|
||||
'email_to_recipients': [['record.multi.1@example.com', 'record.multi.2@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'record.format@example.com',
|
||||
'email_to_mail': 'record.format@example.com',
|
||||
'email_to_recipients': [['record.format@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'record.😊@example.com',
|
||||
'email_to_mail': 'record.😊@example.com',
|
||||
'email_to_recipients': [['record.😊@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'test.record.case@example.com',
|
||||
'email_to_mail': 'test.record.case@example.com',
|
||||
'email_to_recipients': [['test.record.case@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'test.record.weird@example.comweirdformat',
|
||||
'email_to_mail': 'test.record.weird@example.comweirdformat',
|
||||
'email_to_recipients': [['test.record.weird@example.comweirdformat']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
{'email': 'test.record.weird.2@example.com',
|
||||
'email_to_mail': 'test.record.weird.2@example.com',
|
||||
'email_to_recipients': [['test.record.weird.2@example.com']],
|
||||
'failure_type': False,
|
||||
'trace_status': 'sent'},
|
||||
],
|
||||
mailing,
|
||||
test_records,
|
||||
check_mail=True,
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_reply_to_mode_new(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.blacklist', count=5)
|
||||
self.assertEqual(len(recipients), 5)
|
||||
initial_messages = recipients.message_ids
|
||||
mailing.write({
|
||||
'mailing_domain': [('id', 'in', recipients.ids)],
|
||||
'keep_archives': False,
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': self.test_alias.display_name,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
answer_rec = self.gateway_mail_reply_wemail(MAIL_TEMPLATE, recipients[0].email_normalized, target_model=self.test_alias.alias_model_id.model)
|
||||
self.assertTrue(bool(answer_rec))
|
||||
self.assertEqual(answer_rec.name, 'Re: %s' % mailing.subject)
|
||||
self.assertEqual(
|
||||
answer_rec.message_ids.subject, 'Re: %s' % mailing.subject,
|
||||
'Answer should be logged')
|
||||
self.assertEqual(recipients.message_ids, initial_messages)
|
||||
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, replied=1)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_reply_to_mode_update(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.blacklist', count=5)
|
||||
self.assertEqual(len(recipients), 5)
|
||||
mailing.write({
|
||||
'mailing_domain': [('id', 'in', recipients.ids)],
|
||||
'keep_archives': False,
|
||||
'reply_to_mode': 'update',
|
||||
'reply_to': self.test_alias.display_name,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing.action_send_mail()
|
||||
|
||||
answer_rec = self.gateway_mail_reply_wemail(MAIL_TEMPLATE, recipients[0].email_normalized, target_model=self.test_alias.alias_model_id.model)
|
||||
self.assertFalse(bool(answer_rec))
|
||||
self.assertEqual(
|
||||
recipients[0].message_ids[1].subject, mailing.subject,
|
||||
'Should have keep a log (to enable thread-based answer)')
|
||||
self.assertEqual(
|
||||
recipients[0].message_ids[0].subject, 'Re: %s' % mailing.subject,
|
||||
'Answer should be logged')
|
||||
|
||||
self.assertMailingStatistics(mailing, expected=5, delivered=5, sent=5, opened=1, replied=1)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_trace_utm(self):
|
||||
""" Test mailing UTMs are caught on reply"""
|
||||
self._create_mailing_list()
|
||||
self.test_alias.write({
|
||||
'alias_model_id': self.env['ir.model']._get('mailing.test.utm').id
|
||||
})
|
||||
|
||||
source = self.env['utm.source'].create({'name': 'Source test'})
|
||||
medium = self.env['utm.medium'].create({'name': 'Medium test'})
|
||||
campaign = self.env['utm.campaign'].create({'name': 'Campaign test'})
|
||||
subject = 'MassMailingTestUTM'
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'UTMTest',
|
||||
'subject': subject,
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'reply_to_mode': 'new',
|
||||
'reply_to': '%s@%s' % (self.test_alias.alias_name, self.test_alias.alias_domain),
|
||||
'keep_archives': True,
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'contact_list_ids': [(4, self.mailing_list_1.id)],
|
||||
'source_id': source.id,
|
||||
'medium_id': medium.id,
|
||||
'campaign_id': campaign.id
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
traces = self.env['mailing.trace'].search([('model', '=', self.mailing_list_1.contact_ids._name), ('res_id', 'in', self.mailing_list_1.contact_ids.ids)])
|
||||
self.assertEqual(len(traces), 3)
|
||||
|
||||
# simulate response to mailing
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, self.mailing_list_1.contact_ids[1], use_in_reply_to=False)
|
||||
|
||||
mailing_test_utms = self.env['mailing.test.utm'].search([('name', '=', 'Re: %s' % subject)])
|
||||
self.assertEqual(len(mailing_test_utms), 2)
|
||||
for test_utm in mailing_test_utms:
|
||||
self.assertEqual(test_utm.campaign_id, campaign)
|
||||
self.assertEqual(test_utm.source_id, source)
|
||||
self.assertEqual(test_utm.medium_id, medium)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_w_blacklist(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(count=5)
|
||||
|
||||
# blacklist records 2, 3, 4
|
||||
self.env['mail.blacklist'].create({'email': recipients[2].email_normalized})
|
||||
self.env['mail.blacklist'].create({'email': recipients[3].email_normalized})
|
||||
self.env['mail.blacklist'].create({'email': recipients[4].email_normalized})
|
||||
|
||||
# unblacklist record 2
|
||||
self.env['mail.blacklist'].action_remove_with_reason(
|
||||
recipients[2].email_normalized, "human error"
|
||||
)
|
||||
self.env['mail.blacklist'].flush_model(['active'])
|
||||
|
||||
mailing.write({'mailing_domain': [('id', 'in', recipients.ids)]})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com'},
|
||||
{'email': 'test.record.01@test.example.com'},
|
||||
{'email': 'test.record.02@test.example.com'},
|
||||
{'email': 'test.record.03@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'},
|
||||
{'email': 'test.record.04@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'}],
|
||||
mailing, recipients, check_mail=True
|
||||
)
|
||||
self.assertEqual(mailing.canceled, 2)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_w_blacklist_nomixin(self):
|
||||
"""Test that blacklist is applied even if the target model doesn't inherit
|
||||
from mail.thread.blacklist."""
|
||||
test_records = self._create_mailing_test_records(model='mailing.test.simple', count=2)
|
||||
self.mailing_bl.write({
|
||||
'mailing_domain': [('id', 'in', test_records.ids)],
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.simple').id,
|
||||
})
|
||||
self.env['mail.blacklist'].create([{
|
||||
'email': test_records[0].email_from,
|
||||
'active': True,
|
||||
}])
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
self.mailing_bl.action_send_mail()
|
||||
self.assertMailTraces([
|
||||
{'email': email_normalize(test_records[0].email_from), 'trace_status': 'cancel', 'failure_type': 'mail_bl'},
|
||||
{'email': email_normalize(test_records[1].email_from), 'trace_status': 'sent'},
|
||||
], self.mailing_bl, test_records, check_mail=False)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_w_opt_out(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
recipients = self._create_mailing_test_records(model='mailing.test.optout', count=5)
|
||||
|
||||
# optout records 0 and 1
|
||||
(recipients[0] | recipients[1]).write({'opt_out': True})
|
||||
# blacklist records 4
|
||||
self.env['mail.blacklist'].create({'email': recipients[4].email_normalized})
|
||||
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout'),
|
||||
'mailing_domain': [('id', 'in', recipients.ids)]
|
||||
})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test.record.00@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_optout'},
|
||||
{'email': 'test.record.01@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_optout'},
|
||||
{'email': 'test.record.02@test.example.com'},
|
||||
{'email': 'test.record.03@test.example.com'},
|
||||
{'email': 'test.record.04@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_bl'}],
|
||||
mailing, recipients, check_mail=True
|
||||
)
|
||||
self.assertEqual(mailing.canceled, 3)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_w_seenlist(self):
|
||||
"""
|
||||
Tests whether function `_get_seen_list` is correctly able to identify duplicate emails,
|
||||
even through different batches.
|
||||
Mails use different names to make sure they are recognized as duplicates even without being
|
||||
normalized (e.g.: '"jc" <0@example.com>' and '"vd" <0@example.com>' are duplicates)
|
||||
"""
|
||||
BATCH_SIZE = 5
|
||||
names = ['jc', 'vd']
|
||||
emails = [f'test.{i}@example.com' for i in range(BATCH_SIZE)]
|
||||
records = self.env['mailing.test.partner'].create([{
|
||||
'name': f'test_duplicates {i}', 'email_from': f'"{names[i % 2]}" <{emails[i % BATCH_SIZE]}>'
|
||||
} for i in range(20)])
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'mailing_domain': [('name', 'ilike', 'test_duplicates %')],
|
||||
'mailing_model_id': self.env.ref('test_mass_mailing.model_mailing_test_partner').id,
|
||||
'name': 'test duplicates',
|
||||
'subject': 'test duplicates',
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
for i in range(0, 20, BATCH_SIZE):
|
||||
mailing.action_send_mail(records[i:i + BATCH_SIZE].mapped('id'))
|
||||
self.assertEqual(len(self._mails), BATCH_SIZE)
|
||||
self.assertEqual(mailing.canceled, 15)
|
||||
mails_sent = [email_normalize(mail['email_to'][0]) for mail in self._mails]
|
||||
for email in emails:
|
||||
self.assertEqual(mails_sent.count(email), 1)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mailing_w_seenlist_unstored_partner(self):
|
||||
""" Test seen list when partners are not stored. """
|
||||
test_customers = self.env['res.partner'].sudo().create([
|
||||
{'email': f'"Mailing Partner {idx}" <email.from.{idx}@test.example.com',
|
||||
'name': f'Mailing Partner {idx}',
|
||||
} for idx in range(8)
|
||||
])
|
||||
test_records = self.env['mailing.test.partner.unstored'].create([
|
||||
{'email_from': f'email.from.{idx}@test.example.com',
|
||||
'name': f'Mailing Record {idx}',
|
||||
} for idx in range(10)
|
||||
])
|
||||
self.assertEqual(test_records[:8].partner_id, test_customers)
|
||||
self.assertFalse(test_records[9:].partner_id)
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'body_html': '<p>Marketing stuff for ${object.name}</p>',
|
||||
'mailing_domain': [('id', 'in', test_records.ids)],
|
||||
'mailing_model_id': self.env['ir.model']._get_id('mailing.test.partner.unstored'),
|
||||
'name': 'test',
|
||||
'subject': 'Blacklisted',
|
||||
})
|
||||
|
||||
# create existing traces to check the seen list
|
||||
traces = self._create_sent_traces(
|
||||
mailing,
|
||||
test_records[:3]
|
||||
)
|
||||
traces.flush_model()
|
||||
|
||||
# check remaining recipients effectively check seen list
|
||||
mailing.action_put_in_queue()
|
||||
res_ids = mailing._get_remaining_recipients()
|
||||
self.assertEqual(sorted(res_ids), sorted(test_records[3:].ids))
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
self.assertEqual(len(self._mails), 7, 'Mailing: seen list should contain 3 existing traces')
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mailing_mailing_list_optout(self):
|
||||
""" Test mailing list model specific optout behavior """
|
||||
mailing_contact_1 = self.env['mailing.contact'].create({'name': 'test 1A', 'email': 'test@test.example.com'})
|
||||
mailing_contact_2 = self.env['mailing.contact'].create({'name': 'test 1B', 'email': 'test@test.example.com'})
|
||||
mailing_contact_3 = self.env['mailing.contact'].create({'name': 'test 3', 'email': 'test3@test.example.com'})
|
||||
mailing_contact_4 = self.env['mailing.contact'].create({'name': 'test 4', 'email': 'test4@test.example.com'})
|
||||
mailing_contact_5 = self.env['mailing.contact'].create({'name': 'test 5', 'email': 'test5@test.example.com'})
|
||||
|
||||
# create mailing list record
|
||||
mailing_list_1 = self.env['mailing.list'].create({
|
||||
'name': 'A',
|
||||
'contact_ids': [
|
||||
(4, mailing_contact_1.id),
|
||||
(4, mailing_contact_2.id),
|
||||
(4, mailing_contact_3.id),
|
||||
(4, mailing_contact_5.id),
|
||||
]
|
||||
})
|
||||
mailing_list_2 = self.env['mailing.list'].create({
|
||||
'name': 'B',
|
||||
'contact_ids': [
|
||||
(4, mailing_contact_3.id),
|
||||
(4, mailing_contact_4.id),
|
||||
]
|
||||
})
|
||||
# contact_1 is optout but same email is not optout from the same list
|
||||
# contact 3 is optout in list 1 but not in list 2
|
||||
# contact 5 is optout
|
||||
subs = self.env['mailing.contact.subscription'].search([
|
||||
'|', '|',
|
||||
'&', ('contact_id', '=', mailing_contact_1.id), ('list_id', '=', mailing_list_1.id),
|
||||
'&', ('contact_id', '=', mailing_contact_3.id), ('list_id', '=', mailing_list_1.id),
|
||||
'&', ('contact_id', '=', mailing_contact_5.id), ('list_id', '=', mailing_list_1.id)
|
||||
])
|
||||
subs.write({'opt_out': True})
|
||||
|
||||
# create mass mailing record
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'SourceName',
|
||||
'subject': 'MailingSubject',
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'contact_list_ids': [(4, ml.id) for ml in mailing_list_1 | mailing_list_2],
|
||||
})
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertMailTraces(
|
||||
[{'email': 'test@test.example.com', 'trace_status': 'sent'},
|
||||
{'email': 'test@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_dup'},
|
||||
{'email': 'test3@test.example.com'},
|
||||
{'email': 'test4@test.example.com'},
|
||||
{'email': 'test5@test.example.com', 'trace_status': 'cancel', 'failure_type': 'mail_optout'}],
|
||||
mailing,
|
||||
mailing_contact_1 + mailing_contact_2 + mailing_contact_3 + mailing_contact_4 + mailing_contact_5,
|
||||
check_mail=True
|
||||
)
|
||||
self.assertEqual(mailing.canceled, 2)
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mass_mailing')
|
||||
class TestMassMailingServer(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailingServer, cls).setUpClass()
|
||||
cls._init_mail_servers()
|
||||
cls.recipients = cls._create_mailing_test_records(model='mailing.test.optout', count=8)
|
||||
|
||||
def test_mass_mailing_server_archived_usage_protection(self):
|
||||
""" Test the protection against using archived server:
|
||||
- servers used cannot be archived
|
||||
- mailing clone of a mailing with an archived server gets the default one instead
|
||||
"""
|
||||
servers = self.env['ir.mail_server'].create([{
|
||||
'name': 'Server 1',
|
||||
'smtp_host': 'archive-test1.smtp.local',
|
||||
}, {
|
||||
'name': 'Server 2',
|
||||
'smtp_host': 'archive-test2.smtp.local',
|
||||
}])
|
||||
self.env['ir.config_parameter'].set_param('mass_mailing.mail_server_id', servers[0].id)
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': 'specific_user@test.com',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
})
|
||||
|
||||
mailing_clone = mailing.copy()
|
||||
self.assertEqual(mailing_clone.mail_server_id.id, servers[0].id,
|
||||
'The clone of a mailing inherits from the server of the copied mailing')
|
||||
with self.assertRaises(UserError, msg='Servers still used as default and for 2 mailings'):
|
||||
servers.action_archive()
|
||||
self.assertTrue(all(server.active for server in servers), 'All servers must be active')
|
||||
self.env['ir.config_parameter'].set_param('mass_mailing.mail_server_id', False)
|
||||
with self.assertRaises(UserError, msg='Servers still used for 2 mailings'):
|
||||
servers.action_archive()
|
||||
self.assertTrue(all(server.active for server in servers), 'All servers must be active')
|
||||
with self.mock_smtplib_connection():
|
||||
mailing.action_send_mail()
|
||||
with self.assertRaises(UserError, msg='Servers still used for 1 mailings'):
|
||||
servers.action_archive()
|
||||
self.assertTrue(all(server.active for server in servers), 'All servers must be active')
|
||||
with self.mock_smtplib_connection():
|
||||
mailing_clone.action_send_mail()
|
||||
servers.action_archive() # Servers no more used -> no error
|
||||
self.assertFalse(servers.filtered('active'), 'All servers must be archived')
|
||||
self.assertFalse(mailing.copy().mail_server_id,
|
||||
'The clone of a mailing with an archived server gets the default one (none here)')
|
||||
servers[1].action_unarchive()
|
||||
self.env['ir.config_parameter'].set_param('mass_mailing.mail_server_id', servers[1].id)
|
||||
mailing_clone = mailing.copy()
|
||||
self.assertEqual(mailing_clone.mail_server_id.id, servers[1].id,
|
||||
'The clone of a mailing with an archived server gets the default one')
|
||||
mailing_clone.action_archive()
|
||||
with self.assertRaises(UserError, msg='Servers still used as default'):
|
||||
servers.action_archive()
|
||||
self.assertTrue(servers[1].active)
|
||||
self.env['ir.config_parameter'].set_param('mass_mailing.mail_server_id', False)
|
||||
servers.action_archive() # Servers no more used -> no error
|
||||
self.assertFalse(servers.filtered('active'), 'All servers must be archived')
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.addons.mass_mailing.models.mailing')
|
||||
def test_mass_mailing_server_batch(self):
|
||||
"""Test that the right mail server is chosen to send the mailing.
|
||||
|
||||
Test also the envelop and the SMTP headers.
|
||||
"""
|
||||
# Test sending mailing in batch
|
||||
mailings = self.env['mailing.mailing'].create([{
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': 'specific_user@test.com',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
}, {
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': 'unknown_name@test.com',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
}])
|
||||
with self.mock_smtplib_connection():
|
||||
mailings.action_send_mail()
|
||||
self.assertEqual(self.find_mail_server_mocked.call_count, 2, 'Must be called only once per mail from')
|
||||
|
||||
self.assert_email_sent_smtp(
|
||||
smtp_from='specific_user@test.com',
|
||||
message_from='specific_user@test.com',
|
||||
from_filter=self.server_user.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
||||
self.assert_email_sent_smtp(
|
||||
# Must use the bounce address here because the mail server
|
||||
# is configured for the entire domain "test.com"
|
||||
smtp_from=lambda x: 'bounce' in x,
|
||||
message_from='unknown_name@test.com',
|
||||
from_filter=self.server_domain.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.addons.mass_mailing.models.mailing')
|
||||
def test_mass_mailing_server_default(self):
|
||||
# We do not have a mail server for this address email, so fall back to the
|
||||
# "notifications@domain" email.
|
||||
mailings = self.env['mailing.mailing'].create([{
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': '"Testing" <unknow_email@unknow_domain.com>',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
}])
|
||||
|
||||
with self.mock_smtplib_connection():
|
||||
mailings.action_send_mail()
|
||||
|
||||
self.assertEqual(self.find_mail_server_mocked.call_count, 1)
|
||||
self.assert_email_sent_smtp(
|
||||
smtp_from='notifications@test.com',
|
||||
message_from='"Testing" <notifications@test.com>',
|
||||
from_filter=self.server_notification.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
||||
self.assertEqual(self.find_mail_server_mocked.call_count, 1, 'Must be called only once')
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.addons.mass_mailing.models.mailing')
|
||||
def test_mass_mailing_server_forced(self):
|
||||
# We force a mail server on one mailing
|
||||
mailings = self.env['mailing.mailing'].create([{
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': self.server_user.from_filter,
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
}, {
|
||||
'subject': 'Mailing',
|
||||
'body_html': 'Body for <t t-out="object.name" />',
|
||||
'email_from': 'unknow_email@unknow_domain.com',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.test.optout').id,
|
||||
'mail_server_id': self.server_notification.id,
|
||||
}])
|
||||
with self.mock_smtplib_connection():
|
||||
mailings.action_send_mail()
|
||||
self.assertEqual(self.find_mail_server_mocked.call_count, 1, 'Must not be called when mail server is forced')
|
||||
|
||||
self.assert_email_sent_smtp(
|
||||
smtp_from='specific_user@test.com',
|
||||
message_from='specific_user@test.com',
|
||||
from_filter=self.server_user.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
||||
self.assert_email_sent_smtp(
|
||||
smtp_from='unknow_email@unknow_domain.com',
|
||||
message_from='unknow_email@unknow_domain.com',
|
||||
from_filter=self.server_notification.from_filter,
|
||||
emails_count=8,
|
||||
)
|
||||
|
|
@ -0,0 +1,361 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from ast import literal_eval
|
||||
|
||||
from odoo.addons.phone_validation.tools import phone_validation
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassSMSCommon
|
||||
from odoo import exceptions
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mass_mailing', 'mass_mailing_sms')
|
||||
class TestMassSMSInternals(TestMassSMSCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_domain(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Xmas Spam',
|
||||
'subject': 'Xmas Spam',
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms').id,
|
||||
'mailing_type': 'sms',
|
||||
})
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [])
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Xmas Spam',
|
||||
'subject': 'Xmas Spam',
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms.bl').id,
|
||||
'mailing_type': 'sms',
|
||||
})
|
||||
self.assertEqual(literal_eval(mailing.mailing_domain), [('phone_sanitized_blacklisted', '=', False)])
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_internals(self):
|
||||
with self.with_user('user_marketing'):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Xmas Spam',
|
||||
'subject': 'Xmas Spam',
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms').id,
|
||||
'mailing_type': 'sms',
|
||||
'mailing_domain': '%s' % repr([('name', 'ilike', 'MassSMSTest')]),
|
||||
'sms_template_id': self.sms_template.id,
|
||||
'sms_allow_unsubscribe': False,
|
||||
})
|
||||
|
||||
self.assertEqual(mailing.mailing_model_real, 'mail.test.sms')
|
||||
self.assertEqual(mailing.medium_id, self.env.ref('mass_mailing_sms.utm_medium_sms'))
|
||||
self.assertEqual(mailing.body_plaintext, self.sms_template.body)
|
||||
|
||||
remaining_res_ids = mailing._get_remaining_recipients()
|
||||
self.assertEqual(set(remaining_res_ids), set(self.records.ids))
|
||||
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id,
|
||||
'number': self.records_numbers[i],
|
||||
'content': 'Dear %s this is a mass SMS.' % record.display_name
|
||||
} for i, record in enumerate(self.records)],
|
||||
mailing, self.records,
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_internals_errors(self):
|
||||
# same customer, specific different number on record -> should be valid
|
||||
new_record_1 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_nr1',
|
||||
'customer_id': self.partners[0].id,
|
||||
'phone_nbr': '0456999999',
|
||||
})
|
||||
void_record = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_void',
|
||||
'customer_id': False,
|
||||
'phone_nbr': '',
|
||||
})
|
||||
falsy_record_1 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_falsy_1',
|
||||
'customer_id': False,
|
||||
'phone_nbr': 'abcd',
|
||||
})
|
||||
falsy_record_2 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_falsy_2',
|
||||
'customer_id': False,
|
||||
'phone_nbr': '04561122',
|
||||
})
|
||||
bl_record_1 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_bl_1',
|
||||
'customer_id': False,
|
||||
'phone_nbr': '0456110011',
|
||||
})
|
||||
self.env['phone.blacklist'].sudo().create({'number': '0456110011'})
|
||||
# new customer, number already on record -> should be ignored
|
||||
country_be_id = self.env.ref('base.be').id
|
||||
nr2_partner = self.env['res.partner'].create({
|
||||
'name': 'Partner_nr2',
|
||||
'country_id': country_be_id,
|
||||
'mobile': '0456449999',
|
||||
})
|
||||
new_record_2 = self.env['mail.test.sms'].create({
|
||||
'name': 'MassSMSTest_nr2',
|
||||
'customer_id': nr2_partner.id,
|
||||
'phone_nbr': self.records[0].phone_nbr,
|
||||
})
|
||||
records_numbers = self.records_numbers + ['+32456999999']
|
||||
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({'sms_force_send': False}) # force outgoing sms, not sent
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id, 'number': records_numbers[i],
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name}
|
||||
for i, record in enumerate(self.records | new_record_1)],
|
||||
mailing, self.records | new_record_1,
|
||||
)
|
||||
# duplicates
|
||||
self.assertSMSTraces(
|
||||
[{'partner': new_record_2.customer_id, 'number': self.records_numbers[0],
|
||||
'content': 'Dear %s this is a mass SMS' % new_record_2.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_duplicate'}],
|
||||
mailing, new_record_2,
|
||||
)
|
||||
# blacklist
|
||||
self.assertSMSTraces(
|
||||
[{'partner': self.env['res.partner'], 'number': phone_validation.phone_format(bl_record_1.phone_nbr, 'BE', '32', force_format='E164'),
|
||||
'content': 'Dear %s this is a mass SMS' % bl_record_1.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_blacklist'}],
|
||||
mailing, bl_record_1,
|
||||
)
|
||||
# missing number
|
||||
self.assertSMSTraces(
|
||||
[{'partner': self.env['res.partner'], 'number': False,
|
||||
'content': 'Dear %s this is a mass SMS' % void_record.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_number_missing'}],
|
||||
mailing, void_record,
|
||||
)
|
||||
# wrong values
|
||||
self.assertSMSTraces(
|
||||
[{'partner': self.env['res.partner'], 'number': record.phone_nbr,
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_number_format'}
|
||||
for record in falsy_record_1 + falsy_record_2],
|
||||
mailing, falsy_record_1 + falsy_record_2,
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_internals_done_ids(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({'sms_force_send': False}) # check with outgoing traces, not already sent
|
||||
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms(res_ids=self.records[:5].ids)
|
||||
|
||||
traces = self.env['mailing.trace'].search([('mass_mailing_id', 'in', mailing.ids)])
|
||||
self.assertEqual(len(traces), 5)
|
||||
# new traces generated
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id, 'number': self.records_numbers[i],
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name}
|
||||
for i, record in enumerate(self.records[:5])],
|
||||
mailing, self.records[:5],
|
||||
)
|
||||
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms(res_ids=self.records.ids)
|
||||
|
||||
# delete old traces (for testing purpose: ease check by deleting old ones)
|
||||
traces.unlink()
|
||||
# new failed traces generated for duplicates
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id, 'number': self.records_numbers[i],
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name, 'trace_status': 'cancel',
|
||||
'failure_type': 'sms_duplicate'}
|
||||
for i, record in enumerate(self.records[:5])],
|
||||
mailing, self.records[:5],
|
||||
)
|
||||
# new traces generated
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id, 'number': self.records_numbers[i+5],
|
||||
'content': 'Dear %s this is a mass SMS' % record.display_name}
|
||||
for i, record in enumerate(self.records[5:])],
|
||||
mailing, self.records[5:],
|
||||
)
|
||||
|
||||
@mute_logger('odoo.addons.mail.models.mail_render_mixin')
|
||||
def test_mass_sms_test_button(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestButton',
|
||||
'subject': 'Subject {{ object.name }}',
|
||||
'preview': 'Preview {{ object.name }}',
|
||||
'state': 'draft',
|
||||
'mailing_type': 'sms',
|
||||
'body_plaintext': 'Hello {{ object.name }}',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
})
|
||||
mailing_test = self.env['mailing.sms.test'].with_user(self.user_marketing).create({
|
||||
'numbers': '+32456001122',
|
||||
'mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mockSMSGateway():
|
||||
mailing_test.action_send_sms()
|
||||
|
||||
# Test if bad inline_template in the body raises an error
|
||||
mailing.write({
|
||||
'body_plaintext': 'Hello {{ object.name_id.id }}',
|
||||
})
|
||||
|
||||
with self.with_user('user_marketing'):
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.action_send_sms()
|
||||
|
||||
|
||||
@tagged('mass_mailing', 'mass_mailing_sms')
|
||||
class TestMassSMS(TestMassSMSCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
def test_mass_sms_links(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({
|
||||
'body_plaintext': 'Dear {{ object.display_name }} this is a mass SMS with two links http://www.odoo.com/smstest and http://www.odoo.com/smstest/{{ object.name }}',
|
||||
'sms_template_id': False,
|
||||
'sms_force_send': True,
|
||||
'sms_allow_unsubscribe': True,
|
||||
})
|
||||
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id,
|
||||
'number': self.records_numbers[i],
|
||||
'trace_status': 'sent',
|
||||
'content': 'Dear %s this is a mass SMS with two links' % record.display_name
|
||||
} for i, record in enumerate(self.records)],
|
||||
mailing, self.records,
|
||||
sms_links_info=[[
|
||||
('http://www.odoo.com/smstest', True, {}),
|
||||
('http://www.odoo.com/smstest/%s' % record.name, True, {}),
|
||||
# unsubscribe is not shortened and parsed at sending
|
||||
('unsubscribe', False, {}),
|
||||
] for record in self.records],
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mass_sms_partner_only(self):
|
||||
""" Check sending SMS marketing on models having only a partner_id fields
|
||||
set is working. """
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms.partner').id,
|
||||
})
|
||||
|
||||
records = self.env['mail.test.sms.partner'].create([
|
||||
{'name': 'MassSMSTest on %s' % partner.name,
|
||||
'customer_id': partner.id,
|
||||
} for partner in self.partners
|
||||
])
|
||||
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertEqual(len(mailing.mailing_trace_ids), 10)
|
||||
self.assertSMSTraces(
|
||||
[{'partner': record.customer_id,
|
||||
'number': record.customer_id.phone_sanitized,
|
||||
'trace_status': 'sent',
|
||||
'content': 'Dear %s this is a mass SMS with two links' % record.display_name
|
||||
} for record in records],
|
||||
mailing, records,
|
||||
sms_links_info=[[
|
||||
('http://www.odoo.com/smstest', True, {}),
|
||||
('http://www.odoo.com/smstest/%s' % record.id, True, {}),
|
||||
# unsubscribe is not shortened and parsed at sending
|
||||
('unsubscribe', False, {}),
|
||||
] for record in records],
|
||||
)
|
||||
|
||||
# add a new record, send -> sent list should not resend traces
|
||||
new_record = self.env['mail.test.sms.partner'].create([
|
||||
{'name': 'Duplicate SMS on %s' % self.partners[0].name,
|
||||
'customer_id': self.partners[0].id,
|
||||
}
|
||||
])
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertEqual(len(mailing.mailing_trace_ids), 11)
|
||||
self.assertSMSTraces(
|
||||
[{'partner': new_record.customer_id,
|
||||
'number': new_record.customer_id.phone_sanitized,
|
||||
'trace_status': 'sent',
|
||||
'content': 'Dear %s this is a mass SMS with two links' % new_record.display_name
|
||||
}],
|
||||
mailing, new_record,
|
||||
sms_links_info=[[
|
||||
('http://www.odoo.com/smstest', True, {}),
|
||||
('http://www.odoo.com/smstest/%s' % new_record.id, True, {}),
|
||||
# unsubscribe is not shortened and parsed at sending
|
||||
('unsubscribe', False, {}),
|
||||
]],
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mass_sms_partner_only_m2m(self):
|
||||
""" Check sending SMS marketing on models having only a m2m to partners
|
||||
is currently not suppored. """
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms.partner.2many').id,
|
||||
})
|
||||
|
||||
self.env['mail.test.sms.partner.2many'].create([
|
||||
{'name': 'MassSMSTest on %s' % partner.name,
|
||||
'customer_ids': [(4, partner.id)],
|
||||
} for partner in self.partners
|
||||
])
|
||||
|
||||
with self.assertRaises(exceptions.UserError), self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail')
|
||||
def test_mass_sms_w_opt_out(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
recipients = self._create_mailing_sms_test_records(model='mail.test.sms.bl.optout', count=5)
|
||||
|
||||
# optout records 0 and 1
|
||||
(recipients[0] | recipients[1]).write({'opt_out': True})
|
||||
# blacklist records 4
|
||||
# TDE FIXME: sudo should not be necessary
|
||||
self.env['phone.blacklist'].sudo().create({'number': recipients[4].phone_nbr})
|
||||
|
||||
mailing.write({
|
||||
'mailing_model_id': self.env['ir.model']._get('mail.test.sms.bl.optout'),
|
||||
'mailing_domain': [('id', 'in', recipients.ids)],
|
||||
})
|
||||
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
self.assertSMSTraces(
|
||||
[{'number': '+32456000000', 'trace_status': 'cancel', 'failure_type': 'sms_optout'},
|
||||
{'number': '+32456000101', 'trace_status': 'cancel', 'failure_type': 'sms_optout'},
|
||||
{'number': '+32456000202', 'trace_status': 'sent'},
|
||||
{'number': '+32456000303', 'trace_status': 'sent'},
|
||||
{'number': '+32456000404', 'trace_status': 'cancel', 'failure_type': 'sms_blacklist'}],
|
||||
mailing, recipients
|
||||
)
|
||||
self.assertEqual(mailing.canceled, 3)
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import html
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.tests.common import users
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('digest', 'mass_mailing')
|
||||
class TestMailingStatistics(TestMassMailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailingStatistics, cls).setUpClass()
|
||||
|
||||
cls.user_marketing_2 = mail_new_test_user(
|
||||
cls.env,
|
||||
groups='base.group_user,base.group_partner_manager,mass_mailing.group_mass_mailing_user',
|
||||
login='user_marketing_2',
|
||||
name='Marie Marketing',
|
||||
signature='--\nMarie'
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mass_mailing.models.mailing', 'odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_statistics(self):
|
||||
target_records = self._create_mailing_test_records(model='mailing.test.blacklist', count=11)
|
||||
target_records[10]['email_from'] = False # void email should lead to a 'cancel' trace_status
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
mailing.write({'mailing_domain': [('id', 'in', target_records.ids)], 'user_id': self.user_marketing_2.id})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
# simulate some replies and clicks
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, target_records[0], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, target_records[1], use_in_reply_to=True)
|
||||
self.gateway_mail_reply_wrecord(MAIL_TEMPLATE, target_records[2], use_in_reply_to=True)
|
||||
self.gateway_mail_click(mailing, target_records[0], 'https://www.odoo.be')
|
||||
self.gateway_mail_click(mailing, target_records[2], 'https://www.odoo.be')
|
||||
self.gateway_mail_click(mailing, target_records[3], 'https://www.odoo.be')
|
||||
|
||||
# check mailing statistics
|
||||
self.assertEqual(mailing.canceled, 1)
|
||||
self.assertEqual(mailing.clicked, 3)
|
||||
self.assertEqual(mailing.clicks_ratio, 30)
|
||||
self.assertEqual(mailing.delivered, 10)
|
||||
self.assertEqual(mailing.opened, 4)
|
||||
self.assertEqual(mailing.opened_ratio, 40)
|
||||
self.assertEqual(mailing.replied, 3)
|
||||
self.assertEqual(mailing.replied_ratio, 30)
|
||||
self.assertEqual(mailing.sent, 10)
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing._action_send_statistics()
|
||||
|
||||
self.assertEqual(len(self._new_mails), 1, "Mailing: a mail should have been created for statistics")
|
||||
mail = self._new_mails[0]
|
||||
# test email values
|
||||
self.assertEqual(mail.author_id, self.user_marketing_2.partner_id)
|
||||
self.assertEqual(mail.email_from, self.user_marketing_2.email_formatted)
|
||||
self.assertEqual(mail.email_to, self.user_marketing_2.email_formatted)
|
||||
self.assertEqual(mail.reply_to, self.company_admin.partner_id.email_formatted)
|
||||
self.assertEqual(mail.state, 'outgoing')
|
||||
# test body content: KPIs
|
||||
body_html = html.fromstring(mail.body_html)
|
||||
kpi_values = body_html.xpath('//div[@data-field="mail"]//*[hasclass("kpi_value")]/text()')
|
||||
self.assertEqual(
|
||||
[t.strip().strip('%') for t in kpi_values],
|
||||
['100', str(mailing.opened_ratio), str(mailing.replied_ratio)]
|
||||
)
|
||||
# test body content: clicks (a bit hackish but hey we are in stable)
|
||||
kpi_click_values = body_html.xpath('//div[hasclass("global_layout")]/table//tr[contains(@style,"color: #888888")]/td[contains(@style,"width: 30%")]/text()')
|
||||
first_link_value = int(kpi_click_values[0].strip().split()[1].strip('()'))
|
||||
self.assertEqual(first_link_value, mailing.clicked)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mass_mailing.models.mailing', 'odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_statistics_wo_user(self):
|
||||
target_records = self._create_mailing_test_records(model='mailing.test.blacklist', count=10)
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_bl.ids)
|
||||
mailing.write({'mailing_domain': [('id', 'in', target_records.ids)], 'user_id': False})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing.action_send_mail()
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=False):
|
||||
mailing._action_send_statistics()
|
||||
|
||||
self.assertEqual(len(self._new_mails), 1, "Mailing: a mail should have been created for statistics")
|
||||
mail = self._new_mails[0]
|
||||
# test email values
|
||||
self.assertEqual(mail.author_id, self.user_marketing.partner_id)
|
||||
self.assertEqual(mail.email_from, self.user_marketing.email_formatted)
|
||||
self.assertEqual(mail.email_to, self.user_marketing.email_formatted)
|
||||
self.assertEqual(mail.reply_to, self.company_admin.partner_id.email_formatted)
|
||||
self.assertEqual(mail.state, 'outgoing')
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import html
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassSMSCommon
|
||||
from odoo.tests.common import users
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('digest', 'mass_mailing', 'mass_mailing_sms')
|
||||
class TestMailingStatistics(TestMassSMSCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMailingStatistics, cls).setUpClass()
|
||||
|
||||
cls.user_marketing_2 = mail_new_test_user(
|
||||
cls.env,
|
||||
groups='base.group_user,base.group_partner_manager,mass_mailing.group_mass_mailing_user',
|
||||
login='user_marketing_2',
|
||||
name='Marie Marketing',
|
||||
signature='--\nMarie'
|
||||
)
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mass_mailing_sms.models.mailing_mailing', 'odoo.addons.mail.models.mail_mail', 'odoo.addons.mail.models.mail_thread')
|
||||
def test_mailing_statistics_sms(self):
|
||||
mailing = self.env['mailing.mailing'].browse(self.mailing_sms.ids)
|
||||
target_records = self.env['mail.test.sms'].browse(self.records.ids)
|
||||
mailing.write({'mailing_domain': [('id', 'in', target_records.ids)], 'user_id': self.user_marketing_2.id})
|
||||
mailing.action_put_in_queue()
|
||||
with self.mockSMSGateway():
|
||||
mailing.action_send_sms()
|
||||
|
||||
# simulate some replies and clicks
|
||||
self.gateway_sms_click(mailing, target_records[0])
|
||||
self.gateway_sms_click(mailing, target_records[2])
|
||||
self.gateway_sms_click(mailing, target_records[3])
|
||||
|
||||
# check mailing statistics
|
||||
self.assertEqual(mailing.clicked, 3)
|
||||
self.assertEqual(mailing.delivered, 10)
|
||||
self.assertEqual(mailing.opened, 3)
|
||||
self.assertEqual(mailing.opened_ratio, 30)
|
||||
self.assertEqual(mailing.sent, 10)
|
||||
|
||||
with self.mock_mail_gateway(mail_unlink_sent=True):
|
||||
mailing._action_send_statistics()
|
||||
|
||||
self.assertEqual(len(self._new_mails), 1, "Mailing: a mail should have been created for statistics")
|
||||
mail = self._new_mails[0]
|
||||
# test email values
|
||||
self.assertEqual(mail.author_id, self.user_marketing_2.partner_id)
|
||||
self.assertEqual(mail.email_from, self.user_marketing_2.email_formatted)
|
||||
self.assertEqual(mail.email_to, self.user_marketing_2.email_formatted)
|
||||
self.assertEqual(mail.reply_to, self.company_admin.partner_id.email_formatted)
|
||||
self.assertEqual(mail.state, 'outgoing')
|
||||
# test body content: KPIs
|
||||
body_html = html.fromstring(mail.body_html)
|
||||
kpi_values = body_html.xpath('//div[@data-field="sms"]//*[hasclass("kpi_value")]/text()')
|
||||
self.assertEqual(
|
||||
[t.strip().strip('%') for t in kpi_values],
|
||||
['100', str(mailing.clicks_ratio), str(mailing.bounced_ratio)]
|
||||
)
|
||||
# test body content: clicks (a bit hackish but hey we are in stable)
|
||||
kpi_click_values = body_html.xpath('//div[hasclass("global_layout")]/table//tr[contains(@style,"color: #888888")]/td[contains(@style,"width: 30%")]/text()')
|
||||
first_link_value = int(kpi_click_values[0].strip().split()[1].strip('()'))
|
||||
self.assertEqual(first_link_value, mailing.clicked)
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import lxml.html
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.fields import Command
|
||||
from odoo.tests.common import users, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('mailing_manage')
|
||||
class TestMailingTest(TestMassMailCommon):
|
||||
|
||||
@users('user_marketing')
|
||||
@mute_logger('odoo.addons.mail.models.mail_render_mixin')
|
||||
def test_mailing_test_button(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestButton',
|
||||
'subject': 'Subject {{ object.name }}',
|
||||
'preview': 'Preview {{ object.name }}',
|
||||
'state': 'draft',
|
||||
'mailing_type': 'mail',
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'mailing_model_id': self.env['ir.model']._get('res.partner').id,
|
||||
})
|
||||
mailing_test = self.env['mailing.mailing.test'].create({
|
||||
'email_to': 'test@test.com',
|
||||
'mass_mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# not great but matches send_mail_test, maybe that should be a method
|
||||
# on mailing_test?
|
||||
record = self.env[mailing.mailing_model_real].search([], limit=1)
|
||||
first_child = lxml.html.fromstring(self._mails.pop()['body']).xpath('//body/*[1]')[0]
|
||||
self.assertEqual(first_child.tag, 'div')
|
||||
self.assertIn('display:none', first_child.get('style'),
|
||||
"the preview node should be hidden")
|
||||
self.assertEqual(first_child.text.strip(), "Preview " + record.name,
|
||||
"the preview node should contain the preview text")
|
||||
|
||||
# Test if bad inline_template in the subject raises an error
|
||||
mailing.write({'subject': 'Subject {{ object.name_id.id }}'})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad inline_template in the body raises an error
|
||||
mailing.write({
|
||||
'subject': 'Subject {{ object.name }}',
|
||||
'body_html': '<p>Hello <t t-out="object.name_id.id"/></p>',
|
||||
})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
# Test if bad inline_template in the preview raises an error
|
||||
mailing.write({
|
||||
'body_html': '<p>Hello <t t-out="object.name"/></p>',
|
||||
'preview': 'Preview {{ object.name_id.id }}',
|
||||
})
|
||||
with self.mock_mail_gateway(), self.assertRaises(Exception):
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
def test_mailing_test_equals_reality(self):
|
||||
"""
|
||||
Check that both test and real emails will format the qweb and inline placeholders correctly in body and subject.
|
||||
"""
|
||||
self.env['mailing.contact'].search([]).unlink()
|
||||
contact_list = self.env['mailing.list'].create({
|
||||
'name': 'Testers',
|
||||
'contact_ids': [Command.create({
|
||||
'name': 'Mitchell Admin',
|
||||
'email': 'real@real.com',
|
||||
})],
|
||||
})
|
||||
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'TestButton',
|
||||
'subject': 'Subject {{ object.name }} <t t-out="object.name"/>',
|
||||
'state': 'draft',
|
||||
'mailing_type': 'mail',
|
||||
'body_html': '<p>Hello {{ object.name }} <t t-out="object.name"/></p>',
|
||||
'mailing_model_id': self.env['ir.model']._get('mailing.list').id,
|
||||
'contact_list_ids': [contact_list.id],
|
||||
})
|
||||
mailing_test = self.env['mailing.mailing.test'].create({
|
||||
'email_to': 'test@test.com',
|
||||
'mass_mailing_id': mailing.id,
|
||||
})
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
mailing_test.send_mail_test()
|
||||
|
||||
expected_subject = 'Subject Mitchell Admin <t t-out="object.name"/>'
|
||||
expected_body = 'Hello {{ object.name }} Mitchell Admin'
|
||||
|
||||
self.assertSentEmail(self.env.user.partner_id, ['test@test.com'],
|
||||
subject=expected_subject,
|
||||
body_content=expected_body)
|
||||
|
||||
with self.mock_mail_gateway():
|
||||
# send the mailing
|
||||
mailing.action_launch()
|
||||
self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').method_direct_trigger()
|
||||
|
||||
self.assertSentEmail(self.env.user.partner_id, ['real@real.com'],
|
||||
subject=expected_subject,
|
||||
body_content=expected_body)
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
from odoo.addons.test_mail.tests.test_performance import BaseMailPerformance
|
||||
from odoo.tests.common import users, warmup
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestMassMailPerformanceBase(BaseMailPerformance):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestMassMailPerformanceBase, cls).setUpClass()
|
||||
|
||||
cls.user_marketing = mail_new_test_user(
|
||||
cls.env,
|
||||
groups='base.group_user,mass_mailing.group_mass_mailing_user',
|
||||
login='marketing',
|
||||
name='Martial Marketing',
|
||||
signature='--\nMartial'
|
||||
)
|
||||
|
||||
@tagged('mail_performance', 'post_install', '-at_install')
|
||||
class TestMassMailPerformance(TestMassMailPerformanceBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestMassMailPerformance, self).setUp()
|
||||
values = [{
|
||||
'name': 'Recipient %s' % x,
|
||||
'email_from': 'Recipient <rec.%s@example.com>' % x,
|
||||
} for x in range(0, 50)]
|
||||
self.mm_recs = self.env['mailing.performance'].create(values)
|
||||
|
||||
@users('__system__', 'marketing')
|
||||
@warmup
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests')
|
||||
def test_send_mailing(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Test',
|
||||
'subject': 'Test',
|
||||
'body_html': '<p>Hello <a role="button" href="https://www.example.com/foo/bar?baz=qux">quux</a><a role="button" href="/unsubscribe_from_list">Unsubscribe</a></p>',
|
||||
'reply_to_mode': 'new',
|
||||
'mailing_model_id': self.ref('test_mass_mailing.model_mailing_performance'),
|
||||
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
|
||||
})
|
||||
|
||||
# runbot needs +51 compared to local
|
||||
with self.assertQueryCount(__system__=1473, marketing=1474):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(mailing.sent, 50)
|
||||
self.assertEqual(mailing.delivered, 50)
|
||||
|
||||
mails = self.env['mail.mail'].sudo().search([('mailing_id', '=', mailing.id)])
|
||||
self.assertFalse(mails, 'Should have auto-deleted the <mail.mail>')
|
||||
|
||||
|
||||
@tagged('mail_performance', 'post_install', '-at_install')
|
||||
class TestMassMailBlPerformance(TestMassMailPerformanceBase):
|
||||
|
||||
def setUp(self):
|
||||
""" In this setup we prepare 20 blacklist entries. We therefore add
|
||||
20 recipients compared to first test in order to have comparable results. """
|
||||
super(TestMassMailBlPerformance, self).setUp()
|
||||
values = [{
|
||||
'name': 'Recipient %s' % x,
|
||||
'email_from': 'Recipient <rec.%s@example.com>' % x,
|
||||
} for x in range(0, 62)]
|
||||
self.mm_recs = self.env['mailing.performance.blacklist'].create(values)
|
||||
|
||||
for x in range(1, 13):
|
||||
self.env['mail.blacklist'].create({
|
||||
'email': 'rec.%s@example.com' % (x * 5)
|
||||
})
|
||||
self.env.flush_all()
|
||||
|
||||
@users('__system__', 'marketing')
|
||||
@warmup
|
||||
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests')
|
||||
def test_send_mailing_w_bl(self):
|
||||
mailing = self.env['mailing.mailing'].create({
|
||||
'name': 'Test',
|
||||
'subject': 'Test',
|
||||
'body_html': '<p>Hello <a role="button" href="https://www.example.com/foo/bar?baz=qux">quux</a><a role="button" href="/unsubscribe_from_list">Unsubscribe</a></p>',
|
||||
'reply_to_mode': 'new',
|
||||
'mailing_model_id': self.ref('test_mass_mailing.model_mailing_performance_blacklist'),
|
||||
'mailing_domain': [('id', 'in', self.mm_recs.ids)],
|
||||
})
|
||||
|
||||
# runbot needs +51 compared to local
|
||||
with self.assertQueryCount(__system__=1546, marketing=1547):
|
||||
mailing.action_send_mail()
|
||||
|
||||
self.assertEqual(mailing.sent, 50)
|
||||
self.assertEqual(mailing.delivered, 50)
|
||||
|
||||
cancelled_mail_count = self.env['mail.mail'].sudo().search([('mailing_id', '=', mailing.id)])
|
||||
self.assertEqual(len(cancelled_mail_count), 12, 'Should not have auto deleted the blacklisted emails')
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
from odoo.tests import tagged, users
|
||||
|
||||
|
||||
@tagged("utm")
|
||||
class TestUTM(common.TestMassMailCommon):
|
||||
|
||||
@users("employee")
|
||||
def test_utm_source_mixin_name(self):
|
||||
""" Test name management with source mixin, as name should be unique
|
||||
and automatically incremented """
|
||||
sources = self.env["utm.test.source.mixin"].create([
|
||||
{
|
||||
'name': 'Test',
|
||||
'title': 'Test',
|
||||
}
|
||||
for idx in range(5)]
|
||||
)
|
||||
self.assertListEqual(
|
||||
sources.mapped('name'),
|
||||
["Test", "Test [2]", "Test [3]", "Test [4]", "Test [5]"]
|
||||
)
|
||||
|
||||
@users("employee")
|
||||
def test_utm_source_mixin_name_brackets(self):
|
||||
""" Test with brackets """
|
||||
false_dupes = self.env["utm.test.source.mixin"].create([
|
||||
{
|
||||
'name': 'NewTest [2]',
|
||||
'title': 'NewTest',
|
||||
}
|
||||
for idx in range(3)] + [
|
||||
{
|
||||
'name': 'NewTest [3]',
|
||||
'title': 'NewTest',
|
||||
}, {
|
||||
'name': 'NewTest',
|
||||
'title': 'NewTest',
|
||||
}]
|
||||
)
|
||||
self.assertListEqual(
|
||||
false_dupes.mapped('name'),
|
||||
["NewTest [2]", "NewTest", "NewTest [3]", "NewTest [4]", "NewTest [5]"]
|
||||
)
|
||||
|
||||
new_source = self.env["utm.test.source.mixin"].create({
|
||||
"name": "OtherTest [2]",
|
||||
})
|
||||
self.assertEqual(new_source.name, "OtherTest [2]")
|
||||
|
||||
@users("employee")
|
||||
def test_utm_source_mixin_name_cross_model(self):
|
||||
""" Uniqueness of source should be ensured cross models. For this purpose
|
||||
we use two models using the utm.source.mixin, allowing to check conflict
|
||||
management between models. """
|
||||
source_1 = self.env["utm.test.source.mixin"].create({
|
||||
"name": "Test",
|
||||
"title": "Test",
|
||||
})
|
||||
self.assertEqual(source_1.name, "Test")
|
||||
self.assertEqual(source_1.title, "Test")
|
||||
|
||||
source_other_1 = self.env["utm.test.source.mixin.other"].create({
|
||||
"name": "Test",
|
||||
"title": "Test",
|
||||
})
|
||||
self.assertEqual(source_other_1.name, "Test [2]")
|
||||
self.assertEqual(source_other_1.title, "Test")
|
||||
|
||||
source_other_2 = self.env["utm.test.source.mixin.other"].create({
|
||||
"name": "New",
|
||||
"title": "New",
|
||||
})
|
||||
self.assertEqual(source_other_2.name, "New")
|
||||
self.assertEqual(source_other_2.title, "New")
|
||||
|
||||
source_other_2.write({"name": "Test"})
|
||||
self.assertEqual(source_other_2.name, "Test [3]")
|
||||
self.assertEqual(source_other_2.title, "New")
|
||||
|
||||
source_2 = source_1.copy()
|
||||
self.assertEqual(source_2.name, "Test [4]")
|
||||
self.assertEqual(source_2.title, "Test")
|
||||
Loading…
Add table
Add a link
Reference in a new issue