Browse Source

Handle Twilio status callbacks for SMS, WhatsApp and phone call notifications.

pull/409/head
Pēteris Caune 4 years ago
parent
commit
ae01c7a9d1
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
5 changed files with 28 additions and 5 deletions
  1. +1
    -1
      CHANGELOG.md
  2. +11
    -0
      hc/api/tests/test_notification_status.py
  3. +8
    -2
      hc/api/tests/test_notify.py
  4. +2
    -0
      hc/api/transports.py
  5. +6
    -2
      hc/api/views.py

+ 1
- 1
CHANGELOG.md View File

@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
## Improvements ## Improvements
- Django 3.1 - Django 3.1
- Handle status callbacks from Twilio, show SMS delivery failures in Integrations
- Handle status callbacks from Twilio, show delivery failures in Integrations
## v1.16.0 - 2020-08-04 ## v1.16.0 - 2020-08-04


+ 11
- 0
hc/api/tests/test_notification_status.py View File

@ -89,3 +89,14 @@ class NotificationStatusTestCase(BaseTestCase):
self.channel.refresh_from_db() self.channel.refresh_from_db()
self.assertEqual(self.channel.last_error, "Received complaint.") self.assertEqual(self.channel.last_error, "Received complaint.")
self.assertFalse(self.channel.email_verified) self.assertFalse(self.channel.email_verified)
def test_it_handles_twilio_call_status_failed(self):
r = self.client.post(self.url, {"CallStatus": "failed"})
self.assertEqual(r.status_code, 200)
self.n.refresh_from_db()
self.assertEqual(self.n.error, "Delivery failed (status=failed).")
self.channel.refresh_from_db()
self.assertEqual(self.channel.last_error, "Delivery failed (status=failed).")
self.assertTrue(self.channel.email_verified)

+ 8
- 2
hc/api/tests/test_notify.py View File

@ -716,12 +716,15 @@ class NotifyTestCase(BaseTestCase):
mock_post.return_value.status_code = 200 mock_post.return_value.status_code = 200
self.channel.notify(self.check) self.channel.notify(self.check)
self.assertEqual(Notification.objects.count(), 1)
args, kwargs = mock_post.call_args args, kwargs = mock_post.call_args
payload = kwargs["data"] payload = kwargs["data"]
self.assertEqual(payload["To"], "whatsapp:+1234567890") self.assertEqual(payload["To"], "whatsapp:+1234567890")
n = Notification.objects.get()
callback_path = f"/api/v1/notifications/{n.code}/status"
self.assertTrue(payload["StatusCallback"].endswith(callback_path))
# sent SMS counter should go up # sent SMS counter should go up
self.profile.refresh_from_db() self.profile.refresh_from_db()
self.assertEqual(self.profile.sms_sent, 1) self.assertEqual(self.profile.sms_sent, 1)
@ -773,12 +776,15 @@ class NotifyTestCase(BaseTestCase):
mock_post.return_value.status_code = 200 mock_post.return_value.status_code = 200
self.channel.notify(self.check) self.channel.notify(self.check)
assert Notification.objects.count() == 1
args, kwargs = mock_post.call_args args, kwargs = mock_post.call_args
payload = kwargs["data"] payload = kwargs["data"]
self.assertEqual(payload["To"], "+1234567890") self.assertEqual(payload["To"], "+1234567890")
n = Notification.objects.get()
callback_path = f"/api/v1/notifications/{n.code}/status"
self.assertTrue(payload["StatusCallback"].endswith(callback_path))
@patch("hc.api.transports.requests.request") @patch("hc.api.transports.requests.request")
def test_call_limit(self, mock_post): def test_call_limit(self, mock_post):
# At limit already: # At limit already:


+ 2
- 0
hc/api/transports.py View File

@ -500,6 +500,7 @@ class Call(HttpTransport):
"From": settings.TWILIO_FROM, "From": settings.TWILIO_FROM,
"To": self.channel.phone_number, "To": self.channel.phone_number,
"Twiml": twiml, "Twiml": twiml,
"StatusCallback": check.status_url,
} }
return self.post(url, data=data, auth=auth) return self.post(url, data=data, auth=auth)
@ -528,6 +529,7 @@ class WhatsApp(HttpTransport):
"From": "whatsapp:%s" % settings.TWILIO_FROM, "From": "whatsapp:%s" % settings.TWILIO_FROM,
"To": "whatsapp:%s" % self.channel.phone_number, "To": "whatsapp:%s" % self.channel.phone_number,
"Body": text, "Body": text,
"StatusCallback": check.status_url,
} }
return self.post(url, data=data, auth=auth) return self.post(url, data=data, auth=auth)


+ 6
- 2
hc/api/views.py View File

@ -429,16 +429,20 @@ def notification_status(request, code):
error, mark_not_verified = None, False error, mark_not_verified = None, False
# Look for "error" and "unsub" keys:
# Look for "error" and "mark_not_verified" keys:
if request.POST.get("error"): if request.POST.get("error"):
error = request.POST["error"][:200] error = request.POST["error"][:200]
mark_not_verified = request.POST.get("mark_not_verified") mark_not_verified = request.POST.get("mark_not_verified")
# Handle "failed" and "undelivered" callbacks from Twilio
# Handle "MessageStatus" key from Twilio
if request.POST.get("MessageStatus") in ("failed", "undelivered"): if request.POST.get("MessageStatus") in ("failed", "undelivered"):
status = request.POST["MessageStatus"] status = request.POST["MessageStatus"]
error = f"Delivery failed (status={status})." error = f"Delivery failed (status={status})."
# Handle "CallStatus" key from Twilio
if request.POST.get("CallStatus") == "failed":
error = f"Delivery failed (status=failed)."
if error: if error:
notification.error = error notification.error = error
notification.save(update_fields=["error"]) notification.save(update_fields=["error"])


Loading…
Cancel
Save