Files
django-anymail/tests/test_postal_webhooks.py
Mike Edmunds adc92f037b Tests: tox and mock cleanup
* Remove mock dependency
  (mock is now part of standard unittest package)
* Cleanup tox dependency installation logic
2021-06-13 12:13:31 -07:00

325 lines
15 KiB
Python

import json
import unittest
from base64 import b64encode
from datetime import datetime
from unittest.mock import ANY
from django.test import tag
from django.utils.timezone import utc
from anymail.exceptions import AnymailConfigurationError
from anymail.signals import AnymailTrackingEvent
from anymail.webhooks.postal import PostalTrackingWebhookView
from .utils_postal import ClientWithPostalSignature, make_key
from .webhook_cases import WebhookTestCase
@tag('postal')
@unittest.skipUnless(ClientWithPostalSignature, "Install 'cryptography' to run postal webhook tests")
class PostalWebhookSecurityTestCase(WebhookTestCase):
client_class = ClientWithPostalSignature
def setUp(self):
super().setUp()
self.clear_basic_auth()
self.client.set_private_key(make_key())
def test_failed_signature_check(self):
response = self.client.post('/anymail/postal/tracking/',
content_type='application/json', data=json.dumps({'some': 'data'}),
HTTP_X_POSTAL_SIGNATURE=b64encode('invalid'.encode('utf-8')))
self.assertEqual(response.status_code, 400)
response = self.client.post('/anymail/postal/tracking/',
content_type='application/json', data=json.dumps({'some': 'data'}),
HTTP_X_POSTAL_SIGNATURE='garbage')
self.assertEqual(response.status_code, 400)
response = self.client.post('/anymail/postal/tracking/',
content_type='application/json', data=json.dumps({'some': 'data'}),
HTTP_X_POSTAL_SIGNATURE='')
self.assertEqual(response.status_code, 400)
@tag('postal')
@unittest.skipUnless(ClientWithPostalSignature, "Install 'cryptography' to run postal webhook tests")
class PostalDeliveryTestCase(WebhookTestCase):
client_class = ClientWithPostalSignature
def setUp(self):
super().setUp()
self.clear_basic_auth()
self.client.set_private_key(make_key())
def test_bounce_event(self):
raw_event = {
"event": "MessageDelayed",
"timestamp": 1606753101.961181,
"payload": {
"original_message": {
"id": 233843,
"token": "McC2tuqg7mhx",
"direction": "outgoing",
"message_id": "7b82aac4-5d63-41b8-8e35-9faa31a892dc@rp.postal.example.com",
"to": "bounce@example.com",
"from": "sender@example.com",
"subject": "...",
"timestamp": 1606436187.8883688,
"spam_status": "NotChecked",
"tag": None
},
"bounce": {
"id": 233864,
"token": "nII5p0Cp8onV",
"direction": "incoming",
"message_id": "E1kiRR8-0001ay-Iq@example.com",
"to": "bk87jw@psrp.postal.example.com",
"from": None,
"subject": "Mail delivery failed: returning message to sender",
"timestamp": 1606436523.6060522,
"spam_status": "NotChecked",
"tag": None
},
"details": "details",
"output": "server output",
"sent_with_ssl": None,
"timestamp": 1606753101.9110143,
"time": None
},
"uuid": "0fcc831f-92b9-4e2b-97f2-d873abc77fab"
}
response = self.client.post('/anymail/postal/tracking/',
content_type='application/json', data=json.dumps(raw_event))
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=PostalTrackingWebhookView,
event=ANY, esp_name='Postal')
event = kwargs['event']
self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "bounced")
self.assertEqual(event.esp_event, raw_event)
self.assertEqual(event.timestamp, datetime.fromtimestamp(1606753101, tz=utc))
self.assertEqual(event.message_id, 233843)
self.assertEqual(event.event_id, "0fcc831f-92b9-4e2b-97f2-d873abc77fab")
self.assertEqual(event.recipient, "bounce@example.com")
self.assertEqual(event.reject_reason, "bounced")
self.assertEqual(event.description,
"details")
self.assertEqual(event.mta_response,
"server output")
def test_deferred_event(self):
raw_event = {
"event": "MessageDelayed",
"timestamp": 1606753101.961181,
"payload": {
"message": {
"id": 1564,
"token": "Kmo8CRdjuM7B",
"direction": "outgoing",
"message_id": "7b095c0e-2c98-4e68-a41f-7bd217a83925@rp.postal.example.com",
"to": "deferred@example.com",
"from": "test@postal.example.com",
"subject": "Test Message at November 30, 2020 16:03",
"timestamp": 1606752235.195664,
"spam_status": "NotChecked",
"tag": None
},
"status": "SoftFail",
"details": "details",
"output": "server output",
"sent_with_ssl": None,
"timestamp": 1606753101.9110143,
"time": None
},
"uuid": "0fcc831f-92b9-4e2b-97f2-d873abc77fab"
}
response = self.client.post('/anymail/postal/tracking/',
content_type='application/json', data=json.dumps(raw_event))
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=PostalTrackingWebhookView,
event=ANY, esp_name='Postal')
event = kwargs['event']
self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "deferred")
self.assertEqual(event.esp_event, raw_event)
self.assertEqual(event.timestamp, datetime.fromtimestamp(1606753101, tz=utc))
self.assertEqual(event.message_id, 1564)
self.assertEqual(event.event_id, "0fcc831f-92b9-4e2b-97f2-d873abc77fab")
self.assertEqual(event.recipient, "deferred@example.com")
self.assertEqual(event.reject_reason, None)
self.assertEqual(event.description,
"details")
self.assertEqual(event.mta_response,
"server output")
def test_queued_event(self):
raw_event = {
"event": "MessageHeld",
"timestamp": 1606753101.330977,
"payload": {
"message": {
"id": 1568,
"token": "VRvQMS20Bb4Y",
"direction": "outgoing",
"message_id": "ec7b6375-4045-451a-9503-2a23a607c1c1@rp.postal.example.com",
"to": "suppressed@example.com",
"from": "test@example.com",
"subject": "Test Message at November 30, 2020 16:12",
"timestamp": 1606752750.993815,
"spam_status": "NotChecked",
"tag": None
},
"status": "Held",
"details": "Recipient (suppressed@example.com) is on the suppression list",
"output": "server output",
"sent_with_ssl": None,
"timestamp": 1606752751.8933666,
"time": None
},
"uuid": "9be13015-2e54-456c-bf66-eacbe33da824"
}
response = self.client.post('/anymail/postal/tracking/',
content_type='application/json', data=json.dumps(raw_event))
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=PostalTrackingWebhookView,
event=ANY, esp_name='Postal')
event = kwargs['event']
self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "queued")
self.assertEqual(event.esp_event, raw_event)
self.assertEqual(event.timestamp, datetime.fromtimestamp(1606753101, tz=utc))
self.assertEqual(event.message_id, 1568)
self.assertEqual(event.event_id, "9be13015-2e54-456c-bf66-eacbe33da824")
self.assertEqual(event.recipient, "suppressed@example.com")
self.assertEqual(event.reject_reason, None)
self.assertEqual(event.description,
"Recipient (suppressed@example.com) is on the suppression list")
self.assertEqual(event.mta_response,
"server output")
def test_failed_event(self):
raw_event = {
"event": "MessageDeliveryFailed",
"timestamp": 1606753101.084981,
"payload": {
"message": {
"id": 1571,
"token": "MzWWQPubXXWz",
"direction": "outgoing",
"message_id": "cfb29da8ed1e4ed5a6c8a0f24d7a9ef3@rp.postal.example.com",
"to": "failed@example.com",
"from": "test@example.com",
"subject": "Message delivery failed...",
"timestamp": 1606753318.072171,
"spam_status": "NotChecked",
"tag": None
},
"status": "HardFail",
"details": "Could not deliver",
"output": "server output",
"sent_with_ssl": None,
"timestamp": 1606753318.7010343,
"time": None
},
"uuid": "5fec5077-dae7-4989-94d5-e1963f3e9181"
}
response = self.client.post('/anymail/postal/tracking/',
content_type='application/json', data=json.dumps(raw_event))
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=PostalTrackingWebhookView,
event=ANY, esp_name='Postal')
event = kwargs['event']
self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "failed")
self.assertEqual(event.esp_event, raw_event)
self.assertEqual(event.timestamp, datetime.fromtimestamp(1606753101, tz=utc))
self.assertEqual(event.message_id, 1571)
self.assertEqual(event.event_id, "5fec5077-dae7-4989-94d5-e1963f3e9181")
self.assertEqual(event.recipient, "failed@example.com")
self.assertEqual(event.reject_reason, None)
self.assertEqual(event.description,
"Could not deliver")
self.assertEqual(event.mta_response,
"server output")
def test_delivered_event(self):
raw_event = {
"event": "MessageSent",
"timestamp": 1606753101.354368,
"payload": {
"message": {
"id": 1563,
"token": "zw6psSlgo6ki",
"direction": "outgoing",
"message_id": "c462ad36-be49-469c-b7b2-dfd317eb40fa@rp.postal.example.com",
"to": "recipient@example.com",
"from": "test@example.com",
"subject": "Test Message at November 30, 2020 16:01",
"timestamp": 1606752104.699201,
"spam_status": "NotChecked",
"tag": "welcome-email"
},
"status": "Sent",
"details": "Message for recipient@example.com accepted",
"output": "250 2.0.0 OK\n",
"sent_with_ssl": False,
"timestamp": 1606752106.9858062,
"time": 0.89
},
"uuid": "58e8d7ee-2cd5-4db2-9af3-3f436105795a"
}
response = self.client.post('/anymail/postal/tracking/',
content_type='application/json', data=json.dumps(raw_event))
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=PostalTrackingWebhookView,
event=ANY, esp_name='Postal')
event = kwargs['event']
self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "delivered")
self.assertEqual(event.esp_event, raw_event)
self.assertEqual(event.timestamp, datetime.fromtimestamp(1606753101, tz=utc))
self.assertEqual(event.message_id, 1563)
self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.tags, ["welcome-email"])
self.assertEqual(event.metadata, None)
def test_ignore_incoming_events(self):
raw_event = {
"event": "MessageDeliveryFailed",
"timestamp": 1606756014.694645,
"payload": {
"message": {
"id": 1575,
"token": "lPDuNhHfV8aU",
"direction": "incoming",
"message_id": "asdf@other-mta.example.com",
"to": "incoming@example.com",
"from": "sender@example.com",
"subject": "test",
"timestamp": 1606756008.718169,
"spam_status": "NotSpam",
"tag": None
},
"status": "HardFail",
"details": "Received a 400 from https://anymail.example.com/anymail/postal/tracking/.",
"output": "Not found",
"sent_with_ssl": False,
"timestamp": 1606756014.1078613,
"time": 0.15
},
"uuid": "a01724c0-0d1a-4090-89aa-c3da5a683375"
}
response = self.client.post('/anymail/postal/tracking/',
content_type='application/json', data=json.dumps(raw_event))
self.assertEqual(response.status_code, 200)
self.assertEqual(self.tracking_handler.call_count, 0)
def test_misconfigured_inbound(self):
errmsg = "You seem to have set Postal's *inbound* webhook to Anymail's Postal *tracking* webhook URL."
with self.assertRaisesMessage(AnymailConfigurationError, errmsg):
self.client.post('/anymail/postal/tracking/', content_type='application/json',
data=json.dumps({"rcpt_to": "to@example.org"}))