diff --git a/CHANGELOG.md b/CHANGELOG.md index b631f6cf..35c554ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. ### Improvements - Add /api/v1/badges/ endpoint (#552) -- Add ability to edit existing email, Signal, SMS integrations +- Add ability to edit existing email, Signal, SMS, WhatsApp integrations ### Bug Fixes - Add handling for non-latin-1 characters in webhook headers diff --git a/hc/api/models.py b/hc/api/models.py index 709d9d8c..0c66cff0 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -416,7 +416,7 @@ class Channel(models.Model): return {"id": str(self.code), "name": self.name, "kind": self.kind} def is_editable(self): - return self.kind in ("email", "webhook", "sms", "signal") + return self.kind in ("email", "webhook", "sms", "signal", "whatsapp") def assign_all_checks(self): checks = Check.objects.filter(project=self.project) diff --git a/hc/front/tests/test_add_whatsapp.py b/hc/front/tests/test_add_whatsapp.py index cd27c4e4..3ef923ab 100644 --- a/hc/front/tests/test_add_whatsapp.py +++ b/hc/front/tests/test_add_whatsapp.py @@ -1,5 +1,5 @@ from django.test.utils import override_settings -from hc.api.models import Channel +from hc.api.models import Channel, Check from hc.test import BaseTestCase TEST_CREDENTIALS = { @@ -14,11 +14,13 @@ TEST_CREDENTIALS = { class AddWhatsAppTestCase(BaseTestCase): def setUp(self): super().setUp() - self.url = "/projects/%s/add_whatsapp/" % self.project.code + self.check = Check.objects.create(project=self.project) + self.url = f"/projects/{self.project.code}/add_whatsapp/" def test_instructions_work(self): self.client.login(username="alice@example.org", password="password") r = self.client.get(self.url) + self.assertContains(r, "Add WhatsApp Integration") self.assertContains(r, "Get a WhatsApp message") @override_settings(USE_PAYMENTS=True) @@ -50,6 +52,9 @@ class AddWhatsAppTestCase(BaseTestCase): self.assertTrue(c.whatsapp_notify_up) self.assertEqual(c.project, self.project) + # Make sure it calls assign_all_checks + self.assertEqual(c.checks.count(), 1) + def test_it_obeys_up_down_flags(self): form = {"label": "My Phone", "phone": "+1234567890"} diff --git a/hc/front/tests/test_edit_signal.py b/hc/front/tests/test_edit_signal.py index cf73359d..bb9f57d7 100644 --- a/hc/front/tests/test_edit_signal.py +++ b/hc/front/tests/test_edit_signal.py @@ -6,7 +6,7 @@ from hc.test import BaseTestCase @override_settings(SIGNAL_CLI_ENABLED=True) -class AddSignalTestCase(BaseTestCase): +class EditSignalTestCase(BaseTestCase): def setUp(self): super().setUp() self.check = Check.objects.create(project=self.project) @@ -54,9 +54,9 @@ class AddSignalTestCase(BaseTestCase): r = self.client.post(self.url, form) self.assertRedirects(r, self.channels_url) - c = Channel.objects.get() - self.assertFalse(c.signal_notify_down) - self.assertFalse(c.signal_notify_up) + self.channel.refresh_from_db() + self.assertFalse(self.channel.signal_notify_down) + self.assertFalse(self.channel.signal_notify_up) @override_settings(SIGNAL_CLI_ENABLED=False) def test_it_handles_disabled_integration(self): diff --git a/hc/front/tests/test_edit_sms.py b/hc/front/tests/test_edit_sms.py index 6e95c06e..788a8b0c 100644 --- a/hc/front/tests/test_edit_sms.py +++ b/hc/front/tests/test_edit_sms.py @@ -6,7 +6,7 @@ from hc.test import BaseTestCase @override_settings(TWILIO_ACCOUNT="foo", TWILIO_AUTH="foo", TWILIO_FROM="123") -class AddSmsTestCase(BaseTestCase): +class EditSmsTestCase(BaseTestCase): def setUp(self): super().setUp() self.check = Check.objects.create(project=self.project) diff --git a/hc/front/tests/test_edit_whatsapp.py b/hc/front/tests/test_edit_whatsapp.py new file mode 100644 index 00000000..88024093 --- /dev/null +++ b/hc/front/tests/test_edit_whatsapp.py @@ -0,0 +1,80 @@ +import json + +from django.test.utils import override_settings +from hc.api.models import Channel, Check +from hc.test import BaseTestCase + +TEST_CREDENTIALS = { + "TWILIO_ACCOUNT": "foo", + "TWILIO_AUTH": "foo", + "TWILIO_FROM": "123", + "TWILIO_USE_WHATSAPP": True, +} + + +@override_settings(**TEST_CREDENTIALS) +class EditWhatsAppTestCase(BaseTestCase): + def setUp(self): + super().setUp() + self.check = Check.objects.create(project=self.project) + + self.channel = Channel(project=self.project, kind="whatsapp") + self.channel.value = json.dumps( + {"value": "+12345678", "up": True, "down": True} + ) + self.channel.save() + + self.url = f"/integrations/{self.channel.code}/edit/" + + def test_instructions_work(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + self.assertContains(r, "WhatsApp Settings") + self.assertContains(r, "Get a WhatsApp message") + self.assertContains(r, "+12345678") + + def test_it_updates_channel(self): + form = { + "label": "My Phone", + "phone": "+1234567890", + "down": "true", + "up": "false", + } + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url, form) + self.assertRedirects(r, self.channels_url) + + self.channel.refresh_from_db() + self.assertEqual(self.channel.phone_number, "+1234567890") + self.assertEqual(self.channel.name, "My Phone") + self.assertTrue(self.channel.whatsapp_notify_down) + self.assertFalse(self.channel.whatsapp_notify_up) + + # Make sure it does not call assign_all_checks + self.assertFalse(self.channel.checks.exists()) + + def test_it_obeys_up_down_flags(self): + form = {"label": "My Phone", "phone": "+1234567890"} + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url, form) + self.assertRedirects(r, self.channels_url) + + self.channel.refresh_from_db() + self.assertFalse(self.channel.whatsapp_notify_down) + self.assertFalse(self.channel.whatsapp_notify_up) + + @override_settings(TWILIO_USE_WHATSAPP=False) + def test_it_obeys_use_whatsapp_flag(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + self.assertEqual(r.status_code, 404) + + def test_it_requires_rw_access(self): + self.bobs_membership.role = "r" + self.bobs_membership.save() + + self.client.login(username="bob@example.org", password="password") + r = self.client.get(self.url) + self.assertEqual(r.status_code, 403) diff --git a/hc/front/urls.py b/hc/front/urls.py index ad29a646..05405472 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -75,7 +75,7 @@ project_urls = [ path("add_trello/", views.add_trello, name="hc-add-trello"), path("add_victorops/", views.add_victorops, name="hc-add-victorops"), path("add_webhook/", views.webhook_form, name="hc-add-webhook"), - path("add_whatsapp/", views.add_whatsapp, name="hc-add-whatsapp"), + path("add_whatsapp/", views.whatsapp_form, name="hc-add-whatsapp"), path("add_zulip/", views.add_zulip, name="hc-add-zulip"), path("badges/", views.badges, name="hc-badges"), path("checks/", views.my_checks, name="hc-checks"), diff --git a/hc/front/views.py b/hc/front/views.py index b69c85db..0e4be99f 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -1016,6 +1016,8 @@ def edit_channel(request, code): return sms_form(request, channel=channel) if channel.kind == "signal": return signal_form(request, channel=channel) + if channel.kind == "whatsapp": + return whatsapp_form(request, channel=channel) return HttpResponseBadRequest() @@ -1696,28 +1698,42 @@ def add_call(request, code): @require_setting("TWILIO_USE_WHATSAPP") @login_required -def add_whatsapp(request, code): - project = _get_rw_project_for_user(request, code) +def whatsapp_form(request, channel=None, code=None): + is_new = channel is None + if is_new: + project = _get_rw_project_for_user(request, code) + channel = Channel(project=project, kind="whatsapp") + if request.method == "POST": form = forms.PhoneUpDownForm(request.POST) if form.is_valid(): - channel = Channel(project=project, kind="whatsapp") channel.name = form.cleaned_data["label"] channel.value = form.get_json() channel.save() - channel.assign_all_checks() - return redirect("hc-channels", project.code) - else: + if is_new: + channel.assign_all_checks() + return redirect("hc-channels", channel.project.code) + elif is_new: form = forms.PhoneUpDownForm() + else: + form = forms.PhoneUpDownForm( + { + "label": channel.name, + "phone": channel.phone_number, + "up": channel.whatsapp_notify_up, + "down": channel.whatsapp_notify_down, + } + ) ctx = { "page": "channels", - "project": project, + "project": channel.project, "form": form, - "profile": project.owner_profile, + "profile": channel.project.owner_profile, + "is_new": is_new, } - return render(request, "integrations/add_whatsapp.html", ctx) + return render(request, "integrations/whatsapp_form.html", ctx) @require_setting("SIGNAL_CLI_ENABLED") diff --git a/templates/integrations/add_whatsapp.html b/templates/integrations/whatsapp_form.html similarity index 96% rename from templates/integrations/add_whatsapp.html rename to templates/integrations/whatsapp_form.html index fba677f9..ef3134b2 100644 --- a/templates/integrations/add_whatsapp.html +++ b/templates/integrations/whatsapp_form.html @@ -1,7 +1,13 @@ {% extends "base.html" %} {% load humanize static hc_extras %} -{% block title %}Add WhatsApp Integration - {{ site_name }}{% endblock %} +{% block title %} +{% if is_new %} +Add WhatsApp Integration - {% site_name %} +{% else %} +WhatsApp Settings - {% site_name %} +{% endif %} +{% endblock %} {% block content %}