From 885eb9b98a5243fcf0c6397e3cb528624cf9bc92 Mon Sep 17 00:00:00 2001 From: Therry van Neerven Date: Sat, 22 Apr 2023 21:00:05 +0200 Subject: [PATCH] Postmark: workaround invalid "test inbound" data Postmark's "test" button in their inbound settings posts data with attachments that don't match their docs or actual inbound behavior. Accept that and issue a warning. Closes #304 --- CHANGELOG.rst | 6 ++++++ anymail/webhooks/postmark.py | 23 ++++++++++++++++++++-- tests/test_postmark_inbound.py | 35 +++++++++++++++++++++++++++------- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 81d04531..19ab016a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -30,6 +30,12 @@ vNext *Unreleased changes* +Fixes +~~~~~ + +* **Postmark:** Workaround for handling inbound test webhooks. + (`More info `__) + Other ~~~~~ diff --git a/anymail/webhooks/postmark.py b/anymail/webhooks/postmark.py index 4b13aed2..652947d7 100644 --- a/anymail/webhooks/postmark.py +++ b/anymail/webhooks/postmark.py @@ -1,8 +1,9 @@ import json +import warnings from django.utils.dateparse import parse_datetime -from ..exceptions import AnymailConfigurationError +from ..exceptions import AnymailConfigurationError, AnymailWarning from ..inbound import AnymailInboundMessage from ..signals import ( AnymailInboundEvent, @@ -169,7 +170,13 @@ def esp_to_anymail_event(self, esp_event): attachments = [ AnymailInboundMessage.construct_attachment( content_type=attachment["ContentType"], - content=attachment["Content"], + content=( + attachment.get("Content") + # WORKAROUND: + # The test webhooks are not like their real webhooks + # This allows the test webhooks to be parsed. + or attachment["Data"] + ), base64=True, filename=attachment.get("Name", "") or None, content_id=attachment.get("ContentID", "") or None, @@ -177,6 +184,18 @@ def esp_to_anymail_event(self, esp_event): for attachment in esp_event.get("Attachments", []) ] + # Warning to the user regarding the workaround of above. + for attachment in esp_event.get("Attachments", []): + if "Data" in attachment: + warnings.warn( + "Received a test webhook attachment. " + "It is recommended to test with real inbound events. " + "See https://github.com/anymail/django-anymail/issues/304 " + "for more information.", + AnymailWarning, + ) + break + message = AnymailInboundMessage.construct( from_email=self._address(esp_event.get("FromFull")), to=", ".join([self._address(to) for to in esp_event.get("ToFull", [])]), diff --git a/tests/test_postmark_inbound.py b/tests/test_postmark_inbound.py index 18fa3957..7dd8d4b5 100644 --- a/tests/test_postmark_inbound.py +++ b/tests/test_postmark_inbound.py @@ -4,7 +4,7 @@ from django.test import tag -from anymail.exceptions import AnymailConfigurationError +from anymail.exceptions import AnymailConfigurationError, AnymailWarning from anymail.inbound import AnymailInboundMessage from anymail.signals import AnymailInboundEvent from anymail.webhooks.postmark import PostmarkInboundWebhookView @@ -165,14 +165,27 @@ def test_attachments(self): "ContentType": 'message/rfc822; charset="us-ascii"', "ContentLength": len(email_content), }, + # This is an attachement like send by the test webhook + # A workaround is implemented to handle it. + # Once Postmark solves the bug on their side this workaround + # can be reverted. + { + "Name": "test.txt", + "ContentType": "text/plain", + "Data": "VGhpcyBpcyBhdHRhY2htZW50IGNvbnRlbnRzLCBiYXNlLTY0IGVuY29kZWQu", + "ContentLength": 45, + }, ] } - response = self.client.post( - "/anymail/postmark/inbound/", - content_type="application/json", - data=json.dumps(raw_event), - ) + with self.assertWarnsRegex( + AnymailWarning, r"Received a test webhook attachment. " + ): + response = self.client.post( + "/anymail/postmark/inbound/", + content_type="application/json", + data=json.dumps(raw_event), + ) self.assertEqual(response.status_code, 200) kwargs = self.assert_handler_called_once_with( self.inbound_handler, @@ -183,7 +196,7 @@ def test_attachments(self): event = kwargs["event"] message = event.message attachments = message.attachments # AnymailInboundMessage convenience accessor - self.assertEqual(len(attachments), 2) + self.assertEqual(len(attachments), 3) self.assertEqual(attachments[0].get_filename(), "test.txt") self.assertEqual(attachments[0].get_content_type(), "text/plain") self.assertEqual(attachments[0].get_content_text(), "test attachment") @@ -192,6 +205,14 @@ def test_attachments(self): attachments[1].get_content_bytes(), email_content ) + # Attachment of test webhook + self.assertEqual(attachments[2].get_filename(), "test.txt") + self.assertEqual(attachments[2].get_content_type(), "text/plain") + self.assertEqual( + attachments[2].get_content_text(), + "This is attachment contents, base-64 encoded.", + ) + inlines = message.inline_attachments self.assertEqual(len(inlines), 1) inline = inlines["abc123"]