From da88d83089af376596d88fac543df447eb12e9f0 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 17 Oct 2017 20:38:11 +0200 Subject: [PATCH] [10.0][IMP] mail_tracking_mailgun: add partner mail checks --- mail_tracking_mailgun/README.rst | 26 +++- mail_tracking_mailgun/__init__.py | 1 - mail_tracking_mailgun/__manifest__.py | 11 +- mail_tracking_mailgun/i18n/es.po | 133 ++++++++++++++++- mail_tracking_mailgun/models/__init__.py | 3 +- .../models/ir_mail_server.py | 2 +- .../models/mail_tracking_email.py | 58 +++++++- .../models/mail_tracking_event.py | 21 +++ mail_tracking_mailgun/models/res_partner.py | 135 ++++++++++++++++++ mail_tracking_mailgun/tests/__init__.py | 1 - mail_tracking_mailgun/tests/test_mailgun.py | 131 ++++++++++++++++- .../views/mail_tracking_email.xml | 16 +++ mail_tracking_mailgun/views/res_partner.xml | 28 ++++ 13 files changed, 548 insertions(+), 18 deletions(-) create mode 100644 mail_tracking_mailgun/models/mail_tracking_event.py create mode 100644 mail_tracking_mailgun/models/res_partner.py create mode 100644 mail_tracking_mailgun/views/mail_tracking_email.xml create mode 100644 mail_tracking_mailgun/views/res_partner.xml diff --git a/mail_tracking_mailgun/README.rst b/mail_tracking_mailgun/README.rst index dc9f9281..7f377add 100644 --- a/mail_tracking_mailgun/README.rst +++ b/mail_tracking_mailgun/README.rst @@ -29,9 +29,16 @@ You must configure Mailgun webhooks in order to receive mail events: Replace '' with your Odoo install domain name and '' with your database name. -In order to validate Mailgun webhooks you have to save Mailgun api_key in -a system parameter named 'mailgun.apikey'. You can find Mailgun api_key in your -validated sending domain. +In order to validate Mailgun webhooks you have to configure the following system +parameters: + +- `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 ===== @@ -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 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 :alt: Try me on Runbot :target: https://runbot.odoo-community.org/runbot/205/9.0 @@ -70,6 +88,8 @@ Contributors * Antonio Espinosa * Carlos Dauden +* Pedro M. Baeza +* David Vidal * Rafael Blasco Maintainer diff --git a/mail_tracking_mailgun/__init__.py b/mail_tracking_mailgun/__init__.py index 5935294f..ec50cfc0 100644 --- a/mail_tracking_mailgun/__init__.py +++ b/mail_tracking_mailgun/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import models diff --git a/mail_tracking_mailgun/__manifest__.py b/mail_tracking_mailgun/__manifest__.py index 51e92ce5..9fe931e0 100644 --- a/mail_tracking_mailgun/__manifest__.py +++ b/mail_tracking_mailgun/__manifest__.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa +# 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). { "name": "Mail tracking for Mailgun", "summary": "Mail tracking and Mailgun webhooks integration", - "version": "10.0.1.0.0", + "version": "10.0.1.1.0", "category": "Social Network", "website": "https://odoo-community.org/", "author": "Tecnativa, " @@ -15,4 +18,8 @@ "depends": [ "mail_tracking", ], + "data": [ + "views/res_partner.xml", + "views/mail_tracking_email.xml", + ] } diff --git a/mail_tracking_mailgun/i18n/es.po b/mail_tracking_mailgun/i18n/es.po index 9b6e844c..e846022a 100644 --- a/mail_tracking_mailgun/i18n/es.po +++ b/mail_tracking_mailgun/i18n/es.po @@ -8,17 +8,138 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 8.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-09-02 02:43+0000\n" -"PO-Revision-Date: 2016-09-02 02:43+0000\n" -"Last-Translator: OCA Transbot , 2016\n" -"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"POT-Creation-Date: 2017-11-22 09:11+0000\n" +"PO-Revision-Date: 2017-11-22 09:11+0000\n" +"Last-Translator: \n" +"Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: es\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: \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 #: model:ir.model,name:mail_tracking_mailgun.model_mail_tracking_email msgid "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" diff --git a/mail_tracking_mailgun/models/__init__.py b/mail_tracking_mailgun/models/__init__.py index 64dc6c20..b0449e5d 100644 --- a/mail_tracking_mailgun/models/__init__.py +++ b/mail_tracking_mailgun/models/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import ir_mail_server from . import mail_tracking_email +from . import mail_tracking_event +from . import res_partner diff --git a/mail_tracking_mailgun/models/ir_mail_server.py b/mail_tracking_mailgun/models/ir_mail_server.py index 0fe89063..055c8c1e 100644 --- a/mail_tracking_mailgun/models/ir_mail_server.py +++ b/mail_tracking_mailgun/models/ir_mail_server.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - +# Copyright 2016 Tecnativa - Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import json diff --git a/mail_tracking_mailgun/models/mail_tracking_email.py b/mail_tracking_mailgun/models/mail_tracking_email.py index 69a96ad1..2364762b 100644 --- a/mail_tracking_mailgun/models/mail_tracking_email.py +++ b/mail_tracking_mailgun/models/mail_tracking_email.py @@ -1,11 +1,15 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - +# Copyright 2016 Tecnativa - Antonio Espinosa +# Copyright 2017 Tecnativa - David Vidal # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import hashlib import hmac +import json +import requests from datetime import datetime -from odoo import models, api, fields +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError import logging _logger = logging.getLogger(__name__) @@ -40,6 +44,7 @@ class MailTrackingEmail(models.Model): 'complained': 'spam', 'bounced': 'hard_bounce', 'dropped': 'reject', + 'accepted': 'sent', } def _mailgun_event_type_verify(self, event): @@ -58,6 +63,19 @@ class MailTrackingEmail(models.Model): msg='{}{}'.format(str(timestamp), str(token)), 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): event = event or {} api_key = self.env['ir.config_parameter'].get_param('mailgun.apikey') @@ -104,6 +122,7 @@ class MailTrackingEmail(models.Model): 'timestamp': ts, 'time': fields.Datetime.to_string(dt), 'date': fields.Date.to_string(dt), + 'mailgun_id': event.get('id', False) }) # Common field mapping mapping = { @@ -192,3 +211,38 @@ class MailTrackingEmail(models.Model): else: _logger.info("Mailgun: event process '%s'", 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) diff --git a/mail_tracking_mailgun/models/mail_tracking_event.py b/mail_tracking_mailgun/models/mail_tracking_event.py new file mode 100644 index 00000000..de3fe3ed --- /dev/null +++ b/mail_tracking_mailgun/models/mail_tracking_event.py @@ -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 diff --git a/mail_tracking_mailgun/models/res_partner.py b/mail_tracking_mailgun/models/res_partner.py new file mode 100644 index 00000000..9657647c --- /dev/null +++ b/mail_tracking_mailgun/models/res_partner.py @@ -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 diff --git a/mail_tracking_mailgun/tests/__init__.py b/mail_tracking_mailgun/tests/__init__.py index d7169b5d..bfbcec73 100644 --- a/mail_tracking_mailgun/tests/__init__.py +++ b/mail_tracking_mailgun/tests/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import test_mailgun diff --git a/mail_tracking_mailgun/tests/test_mailgun.py b/mail_tracking_mailgun/tests/test_mailgun.py index 5aa6b3bc..4180a8d6 100644 --- a/mail_tracking_mailgun/tests/test_mailgun.py +++ b/mail_tracking_mailgun/tests/test_mailgun.py @@ -1,9 +1,15 @@ # -*- coding: utf-8 -*- -# Copyright 2016 Antonio Espinosa - +# Copyright 2016 Tecnativa - Antonio Espinosa +# Copyright 2017 Tecnativa - David Vidal # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo.tools import mute_logger 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): @@ -26,12 +32,17 @@ class TestMailgun(TransactionCase): self.recipient = u'to@example.com' self.mail, self.tracking_email = self.mail_send() self.api_key = u'key-12345678901234567890123456789012' + self.domain = u'example.com' self.token = u'f1349299097a51b9a7d886fcb5c2735b426ba200ada6e9e149' self.timestamp = u'1471021089' self.signature = ('4fb6d4dbbe10ce5d620265dcd7a3c0b8' 'ca0dede1433103891bc1ae4086e9d5b2') self.env['ir.config_parameter'].set_param( '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 = { 'Message-Id': u'', 'X-Mailgun-Sid': u'WyIwNjgxZSIsICJ0b0BleGFtcGxlLmNvbSIsICI3MG' @@ -51,6 +62,26 @@ class TestMailgun(TransactionCase): 'os_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 ", + "subject": "This is a test" + }, + }, + "event": "delivered" + }] + } def event_search(self, event_type): event = self.env['mail.tracking.event'].search([ @@ -63,6 +94,14 @@ class TestMailgun(TransactionCase): def test_no_api_key(self): self.env['ir.config_parameter'].set_param('mailgun.apikey', '') 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' '.mail_tracking_email') @@ -288,3 +327,93 @@ class TestMailgun(TransactionCase): self.assertEqual(event.error_type, reason) self.assertEqual(event.error_description, code) 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() diff --git a/mail_tracking_mailgun/views/mail_tracking_email.xml b/mail_tracking_mailgun/views/mail_tracking_email.xml new file mode 100644 index 00000000..f796106d --- /dev/null +++ b/mail_tracking_mailgun/views/mail_tracking_email.xml @@ -0,0 +1,16 @@ + + + + + Manual Mailgun check + mail.tracking.email + + + +