[10.0][IMP] mail_tracking_mailgun: add partner mail checks

This commit is contained in:
David 2017-10-17 20:38:11 +02:00 committed by nicolas
parent 3e8273581b
commit 3ca3ee4e49
13 changed files with 548 additions and 18 deletions

View File

@ -29,9 +29,16 @@ You must configure Mailgun webhooks in order to receive mail events:
Replace '<your_domain>' with your Odoo install domain name Replace '<your_domain>' with your Odoo install domain name
and '<your_database>' with your database name. and '<your_database>' with your database name.
In order to validate Mailgun webhooks you have to save Mailgun api_key in In order to validate Mailgun webhooks you have to configure the following system
a system parameter named 'mailgun.apikey'. You can find Mailgun api_key in your parameters:
validated sending domain.
- `mailgun.apikey`: You can find Mailgun api_key in your validated sending
domain.
- `mailgun.api_url`: It should be fine as it is, but it could change in the
future.
- `mailgun.validation_key`: If you want to be able to check mail address
validity you must config this parameter with your account Public Validation
Key.
Usage Usage
===== =====
@ -40,6 +47,17 @@ In your mail tracking status screens (explained on module *mail_tracking*), you
see a more accurate information, like the 'Received' or 'Bounced' status, which are see a more accurate information, like the 'Received' or 'Bounced' status, which are
not usually detected by normal SMTP servers. not usually detected by normal SMTP servers.
It's also possible to make some checks to the partner's email addresses against the Mailgun API:
- Check if the partner's email is in Mailgun's bounced list.
- Check the validity of the partner's mailbox.
- Force the partner's email into Mailgun's bounced list or delete from it.
It's also possible to manually check a message mailgun tracking when the webhook
couldn't be captured. For that, go to that message tracking form, press the
button *Check Mailgun*. It's important to note that tracking events have quite a
short lifespan, so after 24h they won't be recoverable.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot :alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/205/9.0 :target: https://runbot.odoo-community.org/runbot/205/9.0
@ -70,6 +88,8 @@ Contributors
* Antonio Espinosa <antonio.espinosa@tecnativa.com> * Antonio Espinosa <antonio.espinosa@tecnativa.com>
* Carlos Dauden <carlos.dauden@tecnativa.com> * Carlos Dauden <carlos.dauden@tecnativa.com>
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
* David Vidal <david.vidal@tecnativa.com>
* Rafael Blasco <rafael.blasco@tecnativa.com> * Rafael Blasco <rafael.blasco@tecnativa.com>
Maintainer Maintainer

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models from . import models

View File

@ -1,10 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> # Copyright 2016 Tecnativa - Antonio Espinosa
# Copyright 2016 Tecnativa - Carlos Dauden
# Copyright 2017 Tecnativa - Pedro M. Baeza
# Copyright 2017 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
"name": "Mail tracking for Mailgun", "name": "Mail tracking for Mailgun",
"summary": "Mail tracking and Mailgun webhooks integration", "summary": "Mail tracking and Mailgun webhooks integration",
"version": "10.0.1.0.0", "version": "10.0.1.1.0",
"category": "Social Network", "category": "Social Network",
"website": "https://odoo-community.org/", "website": "https://odoo-community.org/",
"author": "Tecnativa, " "author": "Tecnativa, "
@ -15,4 +18,8 @@
"depends": [ "depends": [
"mail_tracking", "mail_tracking",
], ],
"data": [
"views/res_partner.xml",
"views/mail_tracking_email.xml",
]
} }

View File

@ -8,17 +8,138 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Odoo Server 8.0\n" "Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-02 02:43+0000\n" "POT-Creation-Date: 2017-11-22 09:11+0000\n"
"PO-Revision-Date: 2016-09-02 02:43+0000\n" "PO-Revision-Date: 2017-11-22 09:11+0000\n"
"Last-Translator: OCA Transbot <transbot@odoo-community.org>, 2016\n" "Last-Translator: <david.vidal@tecnativa.com>\n"
"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" "Language-Team: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n" "Content-Transfer-Encoding: \n"
"Language: es\n" "Plural-Forms: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/res_partner.py:82
#, python-format
msgid "%s couldn't be verified. Either the request couln't be completed or the mailbox provider doesn't support email verification"
msgstr "%s no ha podido ser verificado. Puede que la petición no se haya completado o que el buzón no soporta validación de correo electrónico"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/res_partner.py:76
#, python-format
msgid "%s failed the mailbox verification. Please check it in order to avoid sending issues"
msgstr "%s no ha pasado la validación de buzón. Compruébela para prevenir problemas con los envíos"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/res_partner.py:69
#, python-format
msgid "%s is not a valid email address. Please check it in order to avoid sending issues"
msgstr "%s no es una dirección válida de correo electrónico. Compruébela para prevenir problemas con los envíos"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/mail_tracking_email.py:75
#, python-format
msgid "A Mailgun domain value is needed!"
msgstr "¡Se necesita un valor de dominio para Mailgun!\""
#. module: mail_tracking_mailgun
#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun
msgid "Check Mailgun"
msgstr "Comprobar Mailgun"
#. module: mail_tracking_mailgun
#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun
msgid "Check email validity"
msgstr "Comprobar validez de email"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/mail_tracking_email.py:236
#, python-format
msgid "Couldn't retrieve Mailgun information"
msgstr "No se ha podido obtener información desde Mailgun"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/res_partner.py:30
#, python-format
msgid ""
"Email has been bounced: %s\n"
"Reason: %s\n"
"Event: %s"
msgstr ""
"El correo ha sido rebotado: %s\n"
"Razón: %s\n"
"Evento: %s"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/res_partner.py:56
#, python-format
msgid "Error %s trying to check mailof connection"
msgstr "Error %s al intentar comprobar el correo electrónicodeconexión"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/mail_tracking_email.py:240
#, python-format
msgid "Event information not longer stored"
msgstr "La información del evento ha caducado"
#. module: mail_tracking_mailgun #. module: mail_tracking_mailgun
#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email #: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email
msgid "MailTracking email" msgid "MailTracking email"
msgstr "MailTracking email" msgstr "MailTracking email"
#. module: mail_tracking_mailgun
#: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_event
msgid "MailTracking event"
msgstr "MailTracking event"
#. module: mail_tracking_mailgun
#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun
msgid "Mailgun"
msgstr "Mailgun"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/res_partner.py:62
#, python-format
msgid "Mailgun Error. Mailbox verification value wasn't returned"
msgstr "Error de Mailgun. No se ha devuelto el valor de verificación de buzón"
#. module: mail_tracking_mailgun
#: model:ir.model.fields,field_description:mail_tracking_mailgun.field_mail_tracking_event_mailgun_id
msgid "Mailgun Event ID"
msgstr "Mailgun Event ID"
#. module: mail_tracking_mailgun
#: model:ir.model,name:mail_tracking_mailgun.model_res_partner
msgid "Partner"
msgstr "Empresa"
#. module: mail_tracking_mailgun
#: model:ir.ui.view,arch_db:mail_tracking_mailgun.mailgun_manual_check
msgid "Re-sync Mailgun"
msgstr "Resincronizar Mailgun"
#. module: mail_tracking_mailgun
#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun
msgid "Set Bounced"
msgstr "Marcar como rebotado"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/mail_tracking_email.py:70
#, python-format
msgid "There is no Mailgun API key!"
msgstr "¡No hay clave de API de Mailgun!"
#. module: mail_tracking_mailgun
#: model:ir.ui.view,arch_db:mail_tracking_mailgun.view_partner_form_mailgun
msgid "Unset Bounced"
msgstr "Desmarcar como rebotado"
#. module: mail_tracking_mailgun
#: code:addons/mail_tracking_mailgun/models/res_partner.py:46
#, python-format
msgid "You need to configure mailgun.validation_key in order to be able to check mails validity"
msgstr "Necesitas configurar mailgun.validation_key para poder comprobar la validez de direcciones de correo"
#. module: mail_tracking_mailgun
#: model:ir.model,name:mail_tracking_mailgun.model_ir_mail_server
msgid "ir.mail_server"
msgstr "ir.mail_server"

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import ir_mail_server from . import ir_mail_server
from . import mail_tracking_email from . import mail_tracking_email
from . import mail_tracking_event
from . import res_partner

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com> # Copyright 2016 Tecnativa - Antonio Espinosa
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import json import json

View File

@ -1,11 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com> # Copyright 2016 Tecnativa - Antonio Espinosa
# Copyright 2017 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import hashlib import hashlib
import hmac import hmac
import json
import requests
from datetime import datetime from datetime import datetime
from odoo import models, api, fields from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
import logging import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -40,6 +44,7 @@ class MailTrackingEmail(models.Model):
'complained': 'spam', 'complained': 'spam',
'bounced': 'hard_bounce', 'bounced': 'hard_bounce',
'dropped': 'reject', 'dropped': 'reject',
'accepted': 'sent',
} }
def _mailgun_event_type_verify(self, event): def _mailgun_event_type_verify(self, event):
@ -58,6 +63,19 @@ class MailTrackingEmail(models.Model):
msg='{}{}'.format(str(timestamp), str(token)), msg='{}{}'.format(str(timestamp), str(token)),
digestmod=hashlib.sha256).hexdigest() digestmod=hashlib.sha256).hexdigest()
def _mailgun_values(self):
icp = self.env['ir.config_parameter']
api_key = icp.get_param('mailgun.apikey')
if not api_key:
raise ValidationError(_('There is no Mailgun API key!'))
api_url = icp.get_param(
'mailgun.api_url', 'https://api.mailgun.net/v3')
domain = icp.get_param('mail.catchall.domain')
if not domain:
raise ValidationError(_('A Mailgun domain value is needed!'))
validation_key = icp.get_param('mailgun.validation_key')
return api_key, api_url, domain, validation_key
def _mailgun_signature_verify(self, event): def _mailgun_signature_verify(self, event):
event = event or {} event = event or {}
api_key = self.env['ir.config_parameter'].get_param('mailgun.apikey') api_key = self.env['ir.config_parameter'].get_param('mailgun.apikey')
@ -104,6 +122,7 @@ class MailTrackingEmail(models.Model):
'timestamp': ts, 'timestamp': ts,
'time': fields.Datetime.to_string(dt), 'time': fields.Datetime.to_string(dt),
'date': fields.Date.to_string(dt), 'date': fields.Date.to_string(dt),
'mailgun_id': event.get('id', False)
}) })
# Common field mapping # Common field mapping
mapping = { mapping = {
@ -192,3 +211,38 @@ class MailTrackingEmail(models.Model):
else: else:
_logger.info("Mailgun: event process '%s'", res) _logger.info("Mailgun: event process '%s'", res)
return res return res
@api.multi
def action_manual_check_mailgun(self):
"""
Manual check against Mailgun API
API Documentation:
https://documentation.mailgun.com/en/latest/api-events.html
"""
api_key, api_url, domain, validation_key = self._mailgun_values()
for tracking in self:
message_id = tracking.mail_message_id.message_id.replace(
"<", "").replace(">", "")
res = requests.get(
'%s/%s/events' % (api_url, domain),
auth=("api", api_key),
params={
"begin": tracking.timestamp,
"ascending": "yes",
"message-id": message_id,
}
)
if not res or res.status_code != 200:
raise ValidationError(_(
"Couldn't retrieve Mailgun information"))
content = json.loads(res.content, res.apparent_encoding)
if "items" not in content:
raise ValidationError(_("Event information not longer stored"))
for item in content["items"]:
if not self.env['mail.tracking.event'].search(
[('mailgun_id', '=', item["id"])]):
mapped_event_type = self._mailgun_event_type_mapping.get(
item["event"]) or False
metadata = self._mailgun_metadata(
mapped_event_type, item, {})
tracking.event_create(mapped_event_type, metadata)

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields
class MailTrackingEvent(models.Model):
_inherit = "mail.tracking.event"
mailgun_id = fields.Char(
string="Mailgun Event ID",
copy="False",
readonly=True,
)
def _process_data(self, tracking_email, metadata, event_type, state):
res = super(MailTrackingEvent, self)._process_data(
tracking_email, metadata, event_type, state)
res.update({'mailgun_id': metadata.get('mailgun_id', False)})
return res

View File

@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Tecnativa - Antonio Espinosa
# Copyright 2016 Tecnativa - Carlos Dauden
# Copyright 2017 Tecnativa - Pedro M. Baeza
# Copyright 2017 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import requests
import json
from openerp import _, api, models
from openerp.exceptions import UserError
class ResPartner(models.Model):
_inherit = 'res.partner'
@api.multi
def email_bounced_set(self, tracking_emails, reason, event=None):
res = super(ResPartner, self).email_bounced_set(
tracking_emails, reason, event=event)
self._email_bounced_set(reason, event)
return res
@api.multi
def _email_bounced_set(self, reason, event):
for partner in self:
if not partner.email:
continue
body = _('Email has been bounced: %s\n'
'Reason: %s\n'
'Event: %s') % (partner.email, reason,
event['Message-Id'] or '')
partner.message_post(body=body)
@api.multi
def check_email_validity(self):
"""
Checks mailbox validity with Mailgun's API
API documentation:
https://documentation.mailgun.com/en/latest/api-email-validation.html
"""
api_key, api_url, domain, validation_key = self.env[
'mail.tracking.email']._mailgun_values()
if not validation_key:
raise UserError(_('You need to configure mailgun.validation_key'
' in order to be able to check mails validity'))
for partner in self:
res = requests.get(
"%s/address/validate" % api_url,
auth=("api", validation_key), params={
"address": partner.email,
"mailbox_verification": True,
})
if not res or res.status_code != 200:
raise UserError(_(
'Error %s trying to '
'check mail' % res.status_code or 'of connection'))
content = json.loads(res.content, res.apparent_encoding)
if 'mailbox_verification' not in content:
raise UserError(
_("Mailgun Error. Mailbox verification value wasn't"
" returned"))
# Not a valid address: API sets 'is_valid' as False
# and 'mailbox_verification' as None
if not content['is_valid']:
partner.email_bounced = True
raise UserError(
_('%s is not a valid email address. Please check it '
'in order to avoid sending issues') % (partner.email))
# If the mailbox is not valid API returns 'mailbox_verification'
# as a string with value 'false'
if content['mailbox_verification'] == 'false':
partner.email_bounced = True
raise UserError(
_('%s failed the mailbox verification. Please check it '
'in order to avoid sending issues') % (partner.email))
# If Mailgun can't complete the validation request the API returns
# 'mailbox_verification' as a string set to 'unknown'
if content['mailbox_verification'] == 'unknown':
raise UserError(
_("%s couldn't be verified. Either the request couln't be "
"completed or the mailbox provider doesn't support "
"email verification") % (partner.email))
@api.multi
def check_email_bounced(self):
"""
Checks if the partner's email is in Mailgun's bounces list
API documentation:
https://documentation.mailgun.com/en/latest/api-suppressions.html
"""
api_key, api_url, domain, validation_key = self.env[
'mail.tracking.email']._mailgun_values()
for partner in self:
res = requests.get(
'%s/%s/bounces/%s' % (api_url, domain, partner.email),
auth=("api", api_key))
if res.status_code == 200 and not partner.email_bounced:
partner.email_bounced = True
elif res.status_code == 404 and partner.email_bounced:
partner.email_bounced = False
@api.multi
def force_set_bounced(self):
"""
Forces partner's email into Mailgun's bounces list
API documentation:
https://documentation.mailgun.com/en/latest/api-suppressions.html
"""
api_key, api_url, domain, validation_key = self.env[
'mail.tracking.email']._mailgun_values()
for partner in self:
res = requests.post(
'%s/%s/bounces' % (api_url, domain),
auth=("api", api_key),
data={'address': partner.email})
partner.email_bounced = (
res.status_code == 200 and not partner.email_bounced)
@api.multi
def force_unset_bounced(self):
"""
Forces partner's email deletion from Mailgun's bounces list
API documentation:
https://documentation.mailgun.com/en/latest/api-suppressions.html
"""
api_key, api_url, domain, validation_key = self.env[
'mail.tracking.email']._mailgun_values()
for partner in self:
res = requests.delete(
'%s/%s/bounces/%s' % (api_url, domain, partner.email),
auth=("api", api_key))
if res.status_code in (200, 404) and partner.email_bounced:
partner.email_bounced = False

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import test_mailgun from . import test_mailgun

View File

@ -1,9 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2016 Antonio Espinosa - <antonio.espinosa@tecnativa.com> # Copyright 2016 Tecnativa - Antonio Espinosa
# Copyright 2017 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tools import mute_logger from odoo.tools import mute_logger
from odoo.tests.common import TransactionCase from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError, ValidationError
import mock
import json
_packagepath = 'odoo.addons.mail_tracking_mailgun'
class TestMailgun(TransactionCase): class TestMailgun(TransactionCase):
@ -26,12 +32,17 @@ class TestMailgun(TransactionCase):
self.recipient = u'to@example.com' self.recipient = u'to@example.com'
self.mail, self.tracking_email = self.mail_send() self.mail, self.tracking_email = self.mail_send()
self.api_key = u'key-12345678901234567890123456789012' self.api_key = u'key-12345678901234567890123456789012'
self.domain = u'example.com'
self.token = u'f1349299097a51b9a7d886fcb5c2735b426ba200ada6e9e149' self.token = u'f1349299097a51b9a7d886fcb5c2735b426ba200ada6e9e149'
self.timestamp = u'1471021089' self.timestamp = u'1471021089'
self.signature = ('4fb6d4dbbe10ce5d620265dcd7a3c0b8' self.signature = ('4fb6d4dbbe10ce5d620265dcd7a3c0b8'
'ca0dede1433103891bc1ae4086e9d5b2') 'ca0dede1433103891bc1ae4086e9d5b2')
self.env['ir.config_parameter'].set_param( self.env['ir.config_parameter'].set_param(
'mailgun.apikey', self.api_key) 'mailgun.apikey', self.api_key)
self.env['ir.config_parameter'].set_param(
'mail.catchall.domain', self.domain)
self.env['ir.config_parameter'].set_param(
'mailgun.validation_key', self.api_key)
self.event = { self.event = {
'Message-Id': u'<xxx.xxx.xxx-openerp-xxx-res.partner@test_db>', 'Message-Id': u'<xxx.xxx.xxx-openerp-xxx-res.partner@test_db>',
'X-Mailgun-Sid': u'WyIwNjgxZSIsICJ0b0BleGFtcGxlLmNvbSIsICI3MG' 'X-Mailgun-Sid': u'WyIwNjgxZSIsICJ0b0BleGFtcGxlLmNvbSIsICI3MG'
@ -51,6 +62,26 @@ class TestMailgun(TransactionCase):
'os_family': False, 'os_family': False,
'ua_family': False, 'ua_family': False,
} }
self.partner = self.env['res.partner'].create({
'name': 'Mr. Odoo',
'email': 'mrodoo@example.com',
})
self.response = {
"items": [{
"log-level": "info",
"id": "oXAVv5URCF-dKv8c6Sa7T",
"timestamp": 1509119329.0,
"message": {
"headers": {
"to": "test@test.com",
"message-id": "test-id@f187c54734e8",
"from": "Mr. Odoo <mrodoo@odoo.com>",
"subject": "This is a test"
},
},
"event": "delivered"
}]
}
def event_search(self, event_type): def event_search(self, event_type):
event = self.env['mail.tracking.event'].search([ event = self.env['mail.tracking.event'].search([
@ -63,6 +94,14 @@ class TestMailgun(TransactionCase):
def test_no_api_key(self): def test_no_api_key(self):
self.env['ir.config_parameter'].set_param('mailgun.apikey', '') self.env['ir.config_parameter'].set_param('mailgun.apikey', '')
self.test_event_delivered() self.test_event_delivered()
with self.assertRaises(ValidationError):
self.env['mail.tracking.email']._mailgun_values()
def test_no_domain(self):
self.env['ir.config_parameter'].set_param('mail.catchall.domain', '')
self.test_event_delivered()
with self.assertRaises(ValidationError):
self.env['mail.tracking.email']._mailgun_values()
@mute_logger('odoo.addons.mail_tracking_mailgun.models' @mute_logger('odoo.addons.mail_tracking_mailgun.models'
'.mail_tracking_email') '.mail_tracking_email')
@ -288,3 +327,93 @@ class TestMailgun(TransactionCase):
self.assertEqual(event.error_type, reason) self.assertEqual(event.error_type, reason)
self.assertEqual(event.error_description, code) self.assertEqual(event.error_description, code)
self.assertEqual(event.error_details, description) self.assertEqual(event.error_details, description)
@mock.patch(_packagepath + '.models.res_partner.requests')
def test_email_validity(self, mock_request):
self.partner.email_bounced = False
self.partner.email = 'info@tecnativa.com'
mock_request.get.return_value.apparent_encoding = 'ascii'
mock_request.get.return_value.status_code = 200
mock_request.get.return_value.content = json.dumps({
'is_valid': True,
'mailbox_verification': 'true',
}, ensure_ascii=True)
self.partner.check_email_validity()
self.assertFalse(self.partner.email_bounced)
self.partner.email = 'xoxoxoxo@tecnativa.com'
# Not a valid mailbox
mock_request.get.return_value.content = json.dumps({
'is_valid': True,
'mailbox_verification': 'false',
}, ensure_ascii=True)
with self.assertRaises(UserError):
self.partner.check_email_validity()
# Not a valid mail address
mock_request.get.return_value.content = json.dumps({
'is_valid': False,
'mailbox_verification': 'false',
}, ensure_ascii=True)
with self.assertRaises(UserError):
self.partner.check_email_validity()
# Unable to fully validate
mock_request.get.return_value.content = json.dumps({
'is_valid': True,
'mailbox_verification': 'unknown',
}, ensure_ascii=True)
with self.assertRaises(UserError):
self.partner.check_email_validity()
self.assertTrue(self.partner.email_bounced)
@mock.patch(_packagepath + '.models.res_partner.requests')
def test_email_validity_exceptions(self, mock_request):
mock_request.get.return_value.status_code = 404
with self.assertRaises(UserError):
self.partner.check_email_validity()
self.env['ir.config_parameter'].set_param('mailgun.validation_key', '')
with self.assertRaises(UserError):
self.partner.check_email_validity()
@mock.patch(_packagepath + '.models.res_partner.requests')
def test_bounced(self, mock_request):
self.partner.email_bounced = True
mock_request.get.return_value.status_code = 404
self.partner.check_email_bounced()
self.assertFalse(self.partner.email_bounced)
mock_request.get.return_value.status_code = 200
self.partner.force_set_bounced()
self.partner.check_email_bounced()
self.assertTrue(self.partner.email_bounced)
mock_request.delete.return_value.status_code = 200
self.partner.force_unset_bounced()
self.assertFalse(self.partner.email_bounced)
def test_email_bounced_set(self):
message_number = len(self.partner.message_ids) + 1
self.partner._email_bounced_set('test_error', self.event)
self.assertEqual(len(self.partner.message_ids), message_number)
self.partner.email = ""
self.partner._email_bounced_set('test_error', self.event)
self.assertEqual(len(self.partner.message_ids), message_number)
@mock.patch(_packagepath + '.models.mail_tracking_email.requests')
def test_manual_check(self, mock_request):
mock_request.get.return_value.content = json.dumps(self.response,
ensure_ascii=True)
mock_request.get.return_value.apparent_encoding = 'ascii'
mock_request.get.return_value.status_code = 200
self.tracking_email.action_manual_check_mailgun()
event = self.env['mail.tracking.event'].search(
[('mailgun_id', '=', self.response['items'][0]['id'])])
self.assertEqual(event.event_type, self.response['items'][0]['event'])
@mock.patch(_packagepath + '.models.mail_tracking_email.requests')
def test_manual_check_exceptions(self, mock_request):
mock_request.get.return_value.status_code = 404
with self.assertRaises(ValidationError):
self.tracking_email.action_manual_check_mailgun()
mock_request.get.return_value.status_code = 200
mock_request.get.return_value.content = json.dumps('{}',
ensure_ascii=True)
mock_request.get.return_value.apparent_encoding = 'ascii'
with self.assertRaises(ValidationError):
self.tracking_email.action_manual_check_mailgun()

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="mailgun_manual_check" model="ir.ui.view">
<field name="name">Manual Mailgun check</field>
<field name="model">mail.tracking.email</field>
<field name="inherit_id" ref="mail_tracking.view_mail_tracking_email_form"/>
<field name="arch" type="xml">
<field name="state" position="before">
<button name="action_manual_check_mailgun"
type="object" string="Re-sync Mailgun"/>
</field>
</field>
</record>
</odoo>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2016 Tecnativa - Pedro M. Baeza
Copyright 2016 Carlos Dauden - Tecnativa <carlos.dauden@tecnativa.com>
Copyright 2017 Tecnativa <vicent.cubellsn@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo>
<record id="view_partner_form_mailgun" model="ir.ui.view">
<field name="name">Partner Mailgun button</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="mail_tracking.view_partner_form"/>
<field name="arch" type="xml">
<field name="email_bounced" position="after">
<label for="check_email_bounced" string="Mailgun"
attrs="{'invisible': [('email', '=', False)]}"/>
<group col="3" name="mailgun_buttons" attrs="{'invisible': [('email', '=', False)]}">
<button name="check_email_bounced" type="object" string="Check Mailgun" class="oe_link"/>
<button name="check_email_validity" type="object" string="Check email validity" class="oe_link"/>
<button name="force_set_bounced" type="object" string="Set Bounced" class="oe_link"
attrs="{'invisible': [('email_bounced', '=', True)]}"/>
<button name="force_unset_bounced" type="object" string="Unset Bounced" class="oe_link"
attrs="{'invisible': [('email_bounced', '=', False)]}"/>
</group>
</field>
</field>
</record>
</odoo>