diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py index 366d1a84870..fa764015c7c 100644 --- a/addons/mail/tests/common.py +++ b/addons/mail/tests/common.py @@ -120,6 +120,30 @@ class MockEmail(common.BaseCase): self.env['mail.thread'].with_context(mail_channel_noautofollow=True).message_process(model, mail) return self.env[target_model].search([(target_field, '=', subject)]) + def gateway_reply_wrecord(self, template, record, use_in_reply_to=True): + """ Simulate a reply through the mail gateway. Usage: giving a record, + find an email sent to him and use its message-ID to simulate a reply. + + Some noise is added in References just to test some robustness. """ + mail_mail = self._find_mail_mail_wrecord(record) + + if use_in_reply_to: + extra = 'In-Reply-To:\r\n\t%s\n' % mail_mail.message_id + else: + disturbing_other_msg_id = '<123456.654321@another.host.com>' + extra = 'References:\r\n\t%s\n\r%s' % (mail_mail.message_id, disturbing_other_msg_id) + + return self.format_and_process( + template, + mail_mail.email_to, + mail_mail.reply_to, + subject='Re: %s' % mail_mail.subject, + extra=extra, + msg_id='<123456.%s.%d@test.example.com>' % (record._name, record.id), + target_model=record._name, + target_field=record._rec_name, + ) + def from_string(self, text): return email.message_from_string(pycompat.to_text(text), policy=email.policy.SMTP) @@ -158,6 +182,14 @@ class MockEmail(common.BaseCase): raise AssertionError('mail.mail not found for message %s / recipients %s' % (mail_message, recipients.ids)) return mail + def _find_mail_mail_wrecord(self, record): + for mail in self._new_mails: + if mail.model == record._name and mail.res_id == record.id: + break + else: + raise AssertionError('mail.mail not found for record %s in %s' % (record, repr([m.email_to for m in self._new_mails]))) + return mail + def assertMailFailed(self, author, recipients, mail_message): mail = self._find_mail(author, recipients, mail_message) self.assertEqual(mail.state, 'exception') diff --git a/addons/mass_mailing/models/mail_thread.py b/addons/mass_mailing/models/mail_thread.py index 3850cf19200..e1370de2447 100644 --- a/addons/mass_mailing/models/mail_thread.py +++ b/addons/mass_mailing/models/mail_thread.py @@ -3,7 +3,7 @@ import datetime -from odoo import api, models, fields +from odoo import api, models, fields, tools BLACKLIST_MAX_BOUNCED_LIMIT = 5 @@ -17,10 +17,14 @@ class MailThread(models.AbstractModel): """ Override to update the parent mailing traces. The parent is found by using the References header of the incoming message and looking for matching message_id in mailing.trace. """ - if message.get('References') and routes: - message_ids = [x.strip() for x in message['References'].split()] - self.env['mailing.trace'].set_opened(mail_message_ids=message_ids) - self.env['mailing.trace'].set_replied(mail_message_ids=message_ids) + if routes: + # even if 'reply_to' in ref (cfr mail/mail_thread) that indicates a new thread redirection + # (aka bypass alias configuration in gateway) consider it as a reply for statistics purpose + thread_references = message_dict['references'] or message_dict['in_reply_to'] + msg_references = tools.mail_header_msgid_re.findall(thread_references) + if msg_references: + self.env['mailing.trace'].set_opened(mail_message_ids=msg_references) + self.env['mailing.trace'].set_replied(mail_message_ids=msg_references) return super(MailThread, self)._message_route_process(message, message_dict, routes) def message_post_with_template(self, template_id, **kwargs): diff --git a/addons/mass_mailing/models/mailing_trace.py b/addons/mass_mailing/models/mailing_trace.py index f79bda46ea6..b4a798e0dee 100644 --- a/addons/mass_mailing/models/mailing_trace.py +++ b/addons/mass_mailing/models/mailing_trace.py @@ -80,10 +80,10 @@ class MailingTrace(models.Model): stat.state = 'ignored' elif stat.exception: stat.state = 'exception' - elif stat.opened or stat.clicked: - stat.state = 'opened' elif stat.replied: stat.state = 'replied' + elif stat.opened or stat.clicked: + stat.state = 'opened' elif stat.bounced: stat.state = 'bounced' elif stat.sent: diff --git a/addons/test_mass_mailing/__init__.py b/addons/test_mass_mailing/__init__.py index dc5e6b693d1..b1475da49a9 100644 --- a/addons/test_mass_mailing/__init__.py +++ b/addons/test_mass_mailing/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import data from . import models diff --git a/addons/test_mass_mailing/data/__init__.py b/addons/test_mass_mailing/data/__init__.py new file mode 100644 index 00000000000..5d1373b6266 --- /dev/null +++ b/addons/test_mass_mailing/data/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import mail_test_data diff --git a/addons/test_mass_mailing/data/mail_test_data.py b/addons/test_mass_mailing/data/mail_test_data.py new file mode 100644 index 00000000000..92183d04ffc --- /dev/null +++ b/addons/test_mass_mailing/data/mail_test_data.py @@ -0,0 +1,42 @@ + +MAIL_TEMPLATE = """Return-Path: +To: {to} +cc: {cc} +Received: by mail1.openerp.com (Postfix, from userid 10002) + id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST) +From: {email_from} +Subject: {subject} +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_Part_4200734_24778174.1344608186754" +Date: Fri, 10 Aug 2012 14:16:26 +0000 +Message-ID: {msg_id} +{extra} +------=_Part_4200734_24778174.1344608186754 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +I would gladly answer to your mass mailing ! + +-- +Your Dear Customer +------=_Part_4200734_24778174.1344608186754 +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + + + + =20 + + =20 + =20 + +

I would gladly answer to your mass mailing !

+ +

--
+ Your Dear Customer +

+ + +------=_Part_4200734_24778174.1344608186754-- +""" diff --git a/addons/test_mass_mailing/tests/__init__.py b/addons/test_mass_mailing/tests/__init__.py index eb8c757f2ba..2391896e3e4 100644 --- a/addons/test_mass_mailing/tests/__init__.py +++ b/addons/test_mass_mailing/tests/__init__.py @@ -5,5 +5,6 @@ from . import test_blacklist from . import test_blacklist_behavior from . import test_blacklist_mixin from . import test_link_tracker +from . import test_mailing_internals from . import test_mass_mailing from . import test_performance diff --git a/addons/test_mass_mailing/tests/test_mailing_internals.py b/addons/test_mass_mailing/tests/test_mailing_internals.py new file mode 100644 index 00000000000..4d91af5b5b6 --- /dev/null +++ b/addons/test_mass_mailing/tests/test_mailing_internals.py @@ -0,0 +1,76 @@ +# -*- 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.addons.test_mass_mailing.data.mail_test_data import MAIL_TEMPLATE + + +class TestMailingInternals(TestMassMailCommon): + + def setUp(self): + super(TestMailingInternals, self).setUp() + + self.env['ir.config_parameter'].set_param('mail.bounce.alias', 'bounce.test') + self.env['ir.config_parameter'].set_param('mail.catchall.alias', 'catchall.test') + self.env['ir.config_parameter'].set_param('mail.catchall.domain', 'test.example.com') + + self.test_alias = self.env['mail.alias'].create({ + 'alias_name': 'test.alias', + 'alias_user_id': False, + 'alias_model_id': self.env['ir.model']._get('mailing.test.simple').id, + 'alias_contact': 'everyone' + }) + + def test_mailing_trace_update(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"