forked from GithubBackups/healthchecks
Add the WEBHOOKS_ENABLED setting
This commit is contained in:
parent
45078e6566
commit
67ff8a9bee
@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Add a section in Docs about running self-hosted instances
|
||||
- Add experimental Dockerfile and docker-compose.yml
|
||||
- Add rate limiting for Pushover notifications (6 notifications / user / minute)
|
||||
- Add the WEBHOOKS_ENABLED setting (#471)
|
||||
|
||||
## Bug Fixes
|
||||
- Fix unwanted HTML escaping in SMS and WhatsApp notifications
|
||||
|
@ -53,3 +53,4 @@ TWILIO_AUTH=
|
||||
TWILIO_FROM=
|
||||
TWILIO_USE_WHATSAPP=False
|
||||
USE_PAYMENTS=False
|
||||
WEBHOOKS_ENABLED=True
|
@ -8,7 +8,7 @@ from django.core import mail
|
||||
from django.utils.timezone import now
|
||||
from hc.api.models import Channel, Check, Notification, TokenBucket
|
||||
from hc.test import BaseTestCase
|
||||
from requests.exceptions import ConnectionError, Timeout
|
||||
from requests.exceptions import Timeout
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
||||
@ -26,309 +26,6 @@ class NotifyTestCase(BaseTestCase):
|
||||
self.channel.save()
|
||||
self.channel.checks.add(self.check)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhook(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
mock_get.return_value.status_code = 200
|
||||
|
||||
self.channel.notify(self.check)
|
||||
mock_get.assert_called_with(
|
||||
"get",
|
||||
"http://example",
|
||||
headers={"User-Agent": "healthchecks.io"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request", side_effect=Timeout)
|
||||
def test_webhooks_handle_timeouts(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
# The transport should have retried 3 times
|
||||
self.assertEqual(mock_get.call_count, 3)
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Connection timed out")
|
||||
|
||||
self.channel.refresh_from_db()
|
||||
self.assertEqual(self.channel.last_error, "Connection timed out")
|
||||
|
||||
@patch("hc.api.transports.requests.request", side_effect=ConnectionError)
|
||||
def test_webhooks_handle_connection_errors(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
# The transport should have retried 3 times
|
||||
self.assertEqual(mock_get.call_count, 3)
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Connection failed")
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_500(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
mock_get.return_value.status_code = 500
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
# The transport should have retried 3 times
|
||||
self.assertEqual(mock_get.call_count, 3)
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Received status code 500")
|
||||
|
||||
@patch("hc.api.transports.requests.request", side_effect=Timeout)
|
||||
def test_webhooks_dont_retry_when_sending_test_notifications(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.channel.notify(self.check, is_test=True)
|
||||
|
||||
# is_test flag is set, the transport should not retry:
|
||||
self.assertEqual(mock_get.call_count, 1)
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Connection timed out")
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_support_variables(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://host/$CODE/$STATUS/$TAG1/$TAG2/?name=$NAME",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.check.name = "Hello World"
|
||||
self.check.tags = "foo bar"
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
url = "http://host/%s/down/foo/bar/?name=Hello%%20World" % self.check.code
|
||||
|
||||
args, kwargs = mock_get.call_args
|
||||
self.assertEqual(args[0], "get")
|
||||
self.assertEqual(args[1], url)
|
||||
self.assertEqual(kwargs["headers"], {"User-Agent": "healthchecks.io"})
|
||||
self.assertEqual(kwargs["timeout"], 5)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_variable_variables(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://host/$$NAMETAG1",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.check.tags = "foo bar"
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
# $$NAMETAG1 should *not* get transformed to "foo"
|
||||
args, kwargs = mock_get.call_args
|
||||
self.assertEqual(args[1], "http://host/$TAG1")
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_support_post(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "POST",
|
||||
"url_down": "http://example.com",
|
||||
"body_down": "The Time Is $NOW",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
args, kwargs = mock_request.call_args
|
||||
self.assertEqual(args[0], "post")
|
||||
self.assertEqual(args[1], "http://example.com")
|
||||
|
||||
# spaces should not have been urlencoded:
|
||||
payload = kwargs["data"].decode()
|
||||
self.assertTrue(payload.startswith("The Time Is 2"))
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_dollarsign_escaping(self, mock_get):
|
||||
# If name or tag contains what looks like a variable reference,
|
||||
# that should be left alone:
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://host/$NAME",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.check.name = "$TAG1"
|
||||
self.check.tags = "foo"
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
url = "http://host/%24TAG1"
|
||||
mock_get.assert_called_with(
|
||||
"get", url, headers={"User-Agent": "healthchecks.io"}, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_up_events(self, mock_get):
|
||||
definition = {
|
||||
"method_up": "GET",
|
||||
"url_up": "http://bar",
|
||||
"body_up": "",
|
||||
"headers_up": {},
|
||||
}
|
||||
self._setup_data("webhook", json.dumps(definition), status="up")
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
mock_get.assert_called_with(
|
||||
"get", "http://bar", headers={"User-Agent": "healthchecks.io"}, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_noop_up_events(self, mock_get):
|
||||
definition = {
|
||||
"method_up": "GET",
|
||||
"url_up": "",
|
||||
"body_up": "",
|
||||
"headers_up": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition), status="up")
|
||||
self.channel.notify(self.check)
|
||||
|
||||
self.assertFalse(mock_get.called)
|
||||
self.assertEqual(Notification.objects.count(), 0)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_unicode_post_body(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "POST",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "(╯°□°)╯︵ ┻━┻",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
args, kwargs = mock_request.call_args
|
||||
|
||||
# unicode should be encoded into utf-8
|
||||
self.assertIsInstance(kwargs["data"], bytes)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_post_headers(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "POST",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "data",
|
||||
"headers_down": {"Content-Type": "application/json"},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
headers = {"User-Agent": "healthchecks.io", "Content-Type": "application/json"}
|
||||
mock_request.assert_called_with(
|
||||
"post", "http://foo.com", data=b"data", headers=headers, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_get_headers(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "",
|
||||
"headers_down": {"Content-Type": "application/json"},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
headers = {"User-Agent": "healthchecks.io", "Content-Type": "application/json"}
|
||||
mock_request.assert_called_with(
|
||||
"get", "http://foo.com", headers=headers, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_allow_user_agent_override(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "",
|
||||
"headers_down": {"User-Agent": "My-Agent"},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
headers = {"User-Agent": "My-Agent"}
|
||||
mock_request.assert_called_with(
|
||||
"get", "http://foo.com", headers=headers, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_support_variables_in_headers(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "",
|
||||
"headers_down": {"X-Message": "$NAME is DOWN"},
|
||||
}
|
||||
|
||||
self._setup_data("webhook", json.dumps(definition))
|
||||
self.check.name = "Foo"
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
headers = {"User-Agent": "healthchecks.io", "X-Message": "Foo is DOWN"}
|
||||
mock_request.assert_called_with(
|
||||
"get", "http://foo.com", headers=headers, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_pd(self, mock_post):
|
||||
self._setup_data("pd", "123")
|
||||
|
344
hc/api/tests/test_notify_webhook.py
Normal file
344
hc/api/tests/test_notify_webhook.py
Normal file
@ -0,0 +1,344 @@
|
||||
# coding: utf-8
|
||||
|
||||
from datetime import timedelta as td
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.utils.timezone import now
|
||||
from hc.api.models import Channel, Check, Notification
|
||||
from hc.test import BaseTestCase
|
||||
from requests.exceptions import ConnectionError, Timeout
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
||||
class NotifyWebhookTestCase(BaseTestCase):
|
||||
def _setup_data(self, value, status="down", email_verified=True):
|
||||
self.check = Check(project=self.project)
|
||||
self.check.status = status
|
||||
self.check.last_ping = now() - td(minutes=61)
|
||||
self.check.save()
|
||||
|
||||
self.channel = Channel(project=self.project)
|
||||
self.channel.kind = "webhook"
|
||||
self.channel.value = value
|
||||
self.channel.email_verified = email_verified
|
||||
self.channel.save()
|
||||
self.channel.checks.add(self.check)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhook(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
mock_get.return_value.status_code = 200
|
||||
|
||||
self.channel.notify(self.check)
|
||||
mock_get.assert_called_with(
|
||||
"get",
|
||||
"http://example",
|
||||
headers={"User-Agent": "healthchecks.io"},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request", side_effect=Timeout)
|
||||
def test_webhooks_handle_timeouts(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
# The transport should have retried 3 times
|
||||
self.assertEqual(mock_get.call_count, 3)
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Connection timed out")
|
||||
|
||||
self.channel.refresh_from_db()
|
||||
self.assertEqual(self.channel.last_error, "Connection timed out")
|
||||
|
||||
@patch("hc.api.transports.requests.request", side_effect=ConnectionError)
|
||||
def test_webhooks_handle_connection_errors(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
# The transport should have retried 3 times
|
||||
self.assertEqual(mock_get.call_count, 3)
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Connection failed")
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_500(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
mock_get.return_value.status_code = 500
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
# The transport should have retried 3 times
|
||||
self.assertEqual(mock_get.call_count, 3)
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Received status code 500")
|
||||
|
||||
@patch("hc.api.transports.requests.request", side_effect=Timeout)
|
||||
def test_webhooks_dont_retry_when_sending_test_notifications(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.channel.notify(self.check, is_test=True)
|
||||
|
||||
# is_test flag is set, the transport should not retry:
|
||||
self.assertEqual(mock_get.call_count, 1)
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Connection timed out")
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_support_variables(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://host/$CODE/$STATUS/$TAG1/$TAG2/?name=$NAME",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.check.name = "Hello World"
|
||||
self.check.tags = "foo bar"
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
url = "http://host/%s/down/foo/bar/?name=Hello%%20World" % self.check.code
|
||||
|
||||
args, kwargs = mock_get.call_args
|
||||
self.assertEqual(args[0], "get")
|
||||
self.assertEqual(args[1], url)
|
||||
self.assertEqual(kwargs["headers"], {"User-Agent": "healthchecks.io"})
|
||||
self.assertEqual(kwargs["timeout"], 5)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_variable_variables(self, mock_get):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://host/$$NAMETAG1",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.check.tags = "foo bar"
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
# $$NAMETAG1 should *not* get transformed to "foo"
|
||||
args, kwargs = mock_get.call_args
|
||||
self.assertEqual(args[1], "http://host/$TAG1")
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_support_post(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "POST",
|
||||
"url_down": "http://example.com",
|
||||
"body_down": "The Time Is $NOW",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
args, kwargs = mock_request.call_args
|
||||
self.assertEqual(args[0], "post")
|
||||
self.assertEqual(args[1], "http://example.com")
|
||||
|
||||
# spaces should not have been urlencoded:
|
||||
payload = kwargs["data"].decode()
|
||||
self.assertTrue(payload.startswith("The Time Is 2"))
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_dollarsign_escaping(self, mock_get):
|
||||
# If name or tag contains what looks like a variable reference,
|
||||
# that should be left alone:
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://host/$NAME",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.check.name = "$TAG1"
|
||||
self.check.tags = "foo"
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
url = "http://host/%24TAG1"
|
||||
mock_get.assert_called_with(
|
||||
"get", url, headers={"User-Agent": "healthchecks.io"}, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_up_events(self, mock_get):
|
||||
definition = {
|
||||
"method_up": "GET",
|
||||
"url_up": "http://bar",
|
||||
"body_up": "",
|
||||
"headers_up": {},
|
||||
}
|
||||
self._setup_data(json.dumps(definition), status="up")
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
mock_get.assert_called_with(
|
||||
"get", "http://bar", headers={"User-Agent": "healthchecks.io"}, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_noop_up_events(self, mock_get):
|
||||
definition = {
|
||||
"method_up": "GET",
|
||||
"url_up": "",
|
||||
"body_up": "",
|
||||
"headers_up": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition), status="up")
|
||||
self.channel.notify(self.check)
|
||||
|
||||
self.assertFalse(mock_get.called)
|
||||
self.assertEqual(Notification.objects.count(), 0)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_unicode_post_body(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "POST",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "(╯°□°)╯︵ ┻━┻",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
args, kwargs = mock_request.call_args
|
||||
|
||||
# unicode should be encoded into utf-8
|
||||
self.assertIsInstance(kwargs["data"], bytes)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_post_headers(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "POST",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "data",
|
||||
"headers_down": {"Content-Type": "application/json"},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
headers = {"User-Agent": "healthchecks.io", "Content-Type": "application/json"}
|
||||
mock_request.assert_called_with(
|
||||
"post", "http://foo.com", data=b"data", headers=headers, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_handle_get_headers(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "",
|
||||
"headers_down": {"Content-Type": "application/json"},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
headers = {"User-Agent": "healthchecks.io", "Content-Type": "application/json"}
|
||||
mock_request.assert_called_with(
|
||||
"get", "http://foo.com", headers=headers, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_allow_user_agent_override(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "",
|
||||
"headers_down": {"User-Agent": "My-Agent"},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
headers = {"User-Agent": "My-Agent"}
|
||||
mock_request.assert_called_with(
|
||||
"get", "http://foo.com", headers=headers, timeout=5
|
||||
)
|
||||
|
||||
@patch("hc.api.transports.requests.request")
|
||||
def test_webhooks_support_variables_in_headers(self, mock_request):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://foo.com",
|
||||
"body_down": "",
|
||||
"headers_down": {"X-Message": "$NAME is DOWN"},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.check.name = "Foo"
|
||||
self.check.save()
|
||||
|
||||
self.channel.notify(self.check)
|
||||
|
||||
headers = {"User-Agent": "healthchecks.io", "X-Message": "Foo is DOWN"}
|
||||
mock_request.assert_called_with(
|
||||
"get", "http://foo.com", headers=headers, timeout=5
|
||||
)
|
||||
|
||||
@override_settings(WEBHOOKS_ENABLED=False)
|
||||
def test_it_requires_webhooks_enabled(self):
|
||||
definition = {
|
||||
"method_down": "GET",
|
||||
"url_down": "http://example",
|
||||
"body_down": "",
|
||||
"headers_down": {},
|
||||
}
|
||||
|
||||
self._setup_data(json.dumps(definition))
|
||||
self.channel.notify(self.check)
|
||||
|
||||
n = Notification.objects.get()
|
||||
self.assertEqual(n.error, "Webhook notifications are not enabled.")
|
@ -232,6 +232,9 @@ class Webhook(HttpTransport):
|
||||
return False
|
||||
|
||||
def notify(self, check):
|
||||
if not settings.WEBHOOKS_ENABLED:
|
||||
return "Webhook notifications are not enabled."
|
||||
|
||||
spec = self.channel.webhook_spec(check.status)
|
||||
if not spec["url"]:
|
||||
return "Empty webhook URL"
|
||||
|
@ -1,3 +1,4 @@
|
||||
from django.test.utils import override_settings
|
||||
from hc.api.models import Channel
|
||||
from hc.test import BaseTestCase
|
||||
|
||||
@ -185,3 +186,9 @@ class AddWebhookTestCase(BaseTestCase):
|
||||
self.client.login(username="bob@example.org", password="password")
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
@override_settings(WEBHOOKS_ENABLED=False)
|
||||
def test_it_handles_disabled_integration(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(self.url)
|
||||
self.assertEqual(r.status_code, 404)
|
||||
|
@ -304,6 +304,7 @@ def index(request):
|
||||
"enable_sms": settings.TWILIO_AUTH is not None,
|
||||
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
|
||||
"enable_trello": settings.TRELLO_APP_KEY is not None,
|
||||
"enable_webhooks": settings.WEBHOOKS_ENABLED is True,
|
||||
"enable_whatsapp": settings.TWILIO_USE_WHATSAPP,
|
||||
"registration_open": settings.REGISTRATION_OPEN,
|
||||
}
|
||||
@ -770,6 +771,7 @@ def channels(request, code):
|
||||
"enable_sms": settings.TWILIO_AUTH is not None,
|
||||
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
|
||||
"enable_trello": settings.TRELLO_APP_KEY is not None,
|
||||
"enable_webhooks": settings.WEBHOOKS_ENABLED is True,
|
||||
"enable_whatsapp": settings.TWILIO_USE_WHATSAPP,
|
||||
"use_payments": settings.USE_PAYMENTS,
|
||||
}
|
||||
@ -931,6 +933,7 @@ def add_email(request, code):
|
||||
return render(request, "integrations/add_email.html", ctx)
|
||||
|
||||
|
||||
@require_setting("WEBHOOKS_ENABLED")
|
||||
@login_required
|
||||
def add_webhook(request, code):
|
||||
project = _get_rw_project_for_user(request, code)
|
||||
|
@ -235,6 +235,9 @@ TWILIO_USE_WHATSAPP = envbool("TWILIO_USE_WHATSAPP", "False")
|
||||
# Trello
|
||||
TRELLO_APP_KEY = os.getenv("TRELLO_APP_KEY")
|
||||
|
||||
# Webhooks
|
||||
WEBHOOKS_ENABLED = envbool("WEBHOOKS_ENABLED", "True")
|
||||
|
||||
# Read additional configuration from hc/local_settings.py if it exists
|
||||
if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")):
|
||||
from .local_settings import *
|
||||
|
@ -321,4 +321,7 @@ scheme.</p>
|
||||
<p>Default: <code>False</code></p>
|
||||
<h2 id="USE_PAYMENTS"><code>USE_PAYMENTS</code></h2>
|
||||
<p>Default: <code>False</code></p>
|
||||
<p>A boolean that turns on/off billing features.</p>
|
||||
<p>A boolean that turns on/off billing features.</p>
|
||||
<h2 id="WEBHOOKS_ENABLED"><code>WEBHOOKS_ENABLED</code></h2>
|
||||
<p>Default: <code>True</code></p>
|
||||
<p>A boolean that turns on/off the Webhooks integration. Enabled by default.</p>
|
@ -508,4 +508,10 @@ Default: `False`
|
||||
|
||||
Default: `False`
|
||||
|
||||
A boolean that turns on/off billing features.
|
||||
A boolean that turns on/off billing features.
|
||||
|
||||
## `WEBHOOKS_ENABLED` {: #WEBHOOKS_ENABLED }
|
||||
|
||||
Default: `True`
|
||||
|
||||
A boolean that turns on/off the Webhooks integration. Enabled by default.
|
||||
|
@ -207,6 +207,7 @@
|
||||
<a href="{% url 'hc-add-email' project.code %}" class="btn btn-primary">Add Integration</a>
|
||||
</li>
|
||||
|
||||
{% if enable_webhooks %}
|
||||
<li>
|
||||
<img src="{% static 'img/integrations/webhook.png' %}"
|
||||
class="icon" alt="Webhook icon" />
|
||||
@ -215,6 +216,7 @@
|
||||
<p>Receive an HTTP callback when a check goes down.</p>
|
||||
<a href="{% url 'hc-add-webhook' project.code %}" class="btn btn-primary">Add Integration</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if enable_apprise %}
|
||||
<li>
|
||||
|
@ -381,6 +381,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if enable_webhooks %}
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
|
||||
<div class="integration">
|
||||
<img src="{% static 'img/integrations/webhook.png' %}" class="icon" alt="" />
|
||||
@ -390,6 +391,7 @@
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
|
||||
{% if enable_slack_btn %}
|
||||
|
Loading…
x
Reference in New Issue
Block a user