Esempio n. 1
0
class NotifyMattermostTestCase(BaseTestCase):
    def setUp(self):
        super().setUp()

        self.check = Check(project=self.project)
        self.check.status = "down"
        self.check.last_ping = now() - td(minutes=61)
        self.check.save()

        self.channel = Channel(project=self.project)
        self.channel.kind = "mattermost"
        self.channel.value = "123"
        self.channel.save()
        self.channel.checks.add(self.check)

    @override_settings(MATTERMOST_ENABLED=False)
    def test_it_requires_mattermost_enabled(self):
        self.channel.notify(self.check)

        n = Notification.objects.get()
        self.assertEqual(n.error, "Mattermost notifications are not enabled.")

    @patch("hc.api.transports.requests.request")
    def test_it_does_not_disable_channel_on_404(self, mock_post):
        mock_post.return_value.status_code = 404

        self.channel.notify(self.check)
        self.channel.refresh_from_db()
        self.assertFalse(self.channel.disabled)
class UpdateChannelNameTestCase(BaseTestCase):
    def setUp(self):
        super(UpdateChannelNameTestCase, self).setUp()
        self.channel = Channel(kind="email", project=self.project)
        self.channel.save()

        self.url = "/integrations/%s/name/" % self.channel.code

    def test_it_works(self):
        payload = {"name": "My work email"}

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, data=payload)
        self.assertRedirects(r, self.channels_url)

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.name, "My work email")

    def test_team_access_works(self):
        payload = {"name": "Bob was here"}

        # Logging in as bob, not alice. Bob has team access so this
        # should work.
        self.client.login(username="******", password="******")
        self.client.post(self.url, data=payload)

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.name, "Bob was here")

    def test_it_checks_ownership(self):
        payload = {"name": "Charlie Sent This"}

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, data=payload)
        self.assertEqual(r.status_code, 404)

    def test_it_handles_missing_uuid(self):
        # Valid UUID but there is no check for it:
        url = "/integrations/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/name/"
        payload = {"name": "Alice Was Here"}

        self.client.login(username="******", password="******")
        r = self.client.post(url, data=payload)
        self.assertEqual(r.status_code, 404)

    def test_it_rejects_get(self):
        self.client.login(username="******", password="******")
        r = self.client.get(self.url)
        self.assertEqual(r.status_code, 405)

    def test_it_requires_rw_access(self):
        self.bobs_membership.rw = False
        self.bobs_membership.save()

        payload = {"name": "My work email"}

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, data=payload)
        self.assertEqual(r.status_code, 403)
Esempio n. 3
0
class EditSignalTestCase(BaseTestCase):
    def setUp(self):
        super().setUp()
        self.check = Check.objects.create(project=self.project)

        self.channel = Channel(project=self.project, kind="signal")
        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="******", password="******")
        r = self.client.get(self.url)
        self.assertContains(r, "Signal Settings")
        self.assertContains(r, "Get a Signal 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="******", 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.signal_notify_down)
        self.assertFalse(self.channel.signal_notify_up)

        # Make sure it does not call assign_all_checks
        self.assertFalse(self.channel.checks.exists())

    @override_settings(SIGNAL_CLI_ENABLED=False)
    def test_it_handles_disabled_integration(self):
        self.client.login(username="******", 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="******", password="******")
        r = self.client.get(self.url)
        self.assertEqual(r.status_code, 403)
Esempio n. 4
0
class BounceTestCase(BaseTestCase):
    def setUp(self):
        super(BounceTestCase, self).setUp()

        self.check = Check(project=self.project, status="up")
        self.check.save()

        self.channel = Channel(project=self.project, kind="email")
        self.channel.value = "*****@*****.**"
        self.channel.email_verified = True
        self.channel.save()

        self.n = Notification(owner=self.check, channel=self.channel)
        self.n.save()

    def test_it_works(self):
        url = "/api/v1/notifications/%s/bounce" % self.n.code
        r = self.client.post(url, "foo", content_type="text/plain")
        self.assertEqual(r.status_code, 200)

        self.n.refresh_from_db()
        self.assertEqual(self.n.error, "foo")

        self.channel.refresh_from_db()
        self.assertFalse(self.channel.email_verified)

    def test_it_checks_ttl(self):
        self.n.created = self.n.created - timedelta(minutes=60)
        self.n.save()

        url = "/api/v1/notifications/%s/bounce" % self.n.code
        r = self.client.post(url, "foo", content_type="text/plain")
        self.assertEqual(r.status_code, 403)

    def test_it_handles_long_payload(self):
        url = "/api/v1/notifications/%s/bounce" % self.n.code
        payload = "A" * 500
        r = self.client.post(url, payload, content_type="text/plain")
        self.assertEqual(r.status_code, 200)

    def test_it_handles_missing_notification(self):
        fake_code = "07c2f548-9850-4b27-af5d-6c9dc157ec02"
        url = "/api/v1/notifications/%s/bounce" % fake_code
        r = self.client.post(url, "", content_type="text/plain")
        self.assertEqual(r.status_code, 404)

    def test_it_requires_post(self):
        url = "/api/v1/notifications/%s/bounce" % self.n.code
        r = self.client.get(url)
        self.assertEqual(r.status_code, 405)
Esempio n. 5
0
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=10,
        )

    @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", side_effect=ContentDecodingError)
    def test_webhooks_handle_content_decoding_error(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, "Failed to decode response")

    @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"], 10)

    @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=10
        )

    @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=10
        )

    @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=10
        )

    @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=10
        )

    @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=10
        )

    @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=10
        )

    @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.")

    @patch("hc.api.transports.requests.request")
    def test_webhooks_handle_non_ascii_in_headers(self, mock_request):
        definition = {
            "method_down": "GET",
            "url_down": "http://foo.com",
            "headers_down": {"X-Foo": "bār"},
            "body_down": "",
        }

        self._setup_data(json.dumps(definition))
        self.check.save()

        self.channel.notify(self.check)
        args, kwargs = mock_request.call_args

        self.assertEqual(kwargs["headers"]["X-Foo"], "bār")

    @patch("hc.api.transports.requests.request")
    def test_webhooks_handle_latin1_in_headers(self, mock_request):
        definition = {
            "method_down": "GET",
            "url_down": "http://foo.com",
            "headers_down": {"X-Foo": "½"},
            "body_down": "",
        }

        self._setup_data(json.dumps(definition))
        self.check.save()

        self.channel.notify(self.check)
        args, kwargs = mock_request.call_args

        self.assertEqual(kwargs["headers"]["X-Foo"], "½")
Esempio n. 6
0
class NotificationStatusTestCase(BaseTestCase):
    def setUp(self):
        super().setUp()

        self.check = Check(project=self.project, status="up")
        self.check.save()

        self.channel = Channel(project=self.project, kind="email")
        self.channel.value = "*****@*****.**"
        self.channel.email_verified = True
        self.channel.save()

        self.n = Notification(owner=self.check, channel=self.channel)
        self.n.save()

        self.url = "/api/v1/notifications/%s/status" % self.n.code

    def test_it_handles_twilio_failed_status(self):
        r = self.csrf_client.post(self.url, {"MessageStatus": "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)

    def test_it_handles_twilio_undelivered_status(self):
        r = self.csrf_client.post(self.url, {"MessageStatus": "undelivered"})
        self.assertEqual(r.status_code, 200)

        self.n.refresh_from_db()
        self.assertEqual(self.n.error, "Delivery failed (status=undelivered).")

        self.channel.refresh_from_db()
        self.assertIn("status=undelivered", self.channel.last_error)

    def test_it_handles_twilio_delivered_status(self):
        r = self.csrf_client.post(self.url, {"MessageStatus": "delivered"})
        self.assertEqual(r.status_code, 200)

        self.n.refresh_from_db()
        self.assertEqual(self.n.error, "")

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.last_error, "")

    def test_it_checks_ttl(self):
        self.n.created = self.n.created - timedelta(minutes=61)
        self.n.save()

        r = self.csrf_client.post(self.url, {"MessageStatus": "failed"})
        self.assertEqual(r.status_code, 200)

        # The notification should not have the error field set:
        self.n.refresh_from_db()
        self.assertEqual(self.n.error, "")

    def test_it_handles_missing_notification(self):
        fake_code = "07c2f548-9850-4b27-af5d-6c9dc157ec02"
        url = f"/api/v1/notifications/{fake_code}/status"
        r = self.csrf_client.post(url, {"MessageStatus": "failed"})
        self.assertEqual(r.status_code, 200)

    def test_it_requires_post(self):
        r = self.csrf_client.get(self.url)
        self.assertEqual(r.status_code, 405)

    def test_it_handles_error_key(self):
        r = self.csrf_client.post(self.url, {"error": "Something went wrong."})
        self.assertEqual(r.status_code, 200)

        self.n.refresh_from_db()
        self.assertEqual(self.n.error, "Something went wrong.")

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.last_error, "Something went wrong.")
        self.assertTrue(self.channel.email_verified)

    def test_it_handles_mark_not_verified_key(self):
        payload = {"error": "Received complaint.", "mark_not_verified": "1"}

        r = self.csrf_client.post(self.url, payload)
        self.assertEqual(r.status_code, 200)

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.last_error, "Received complaint.")
        self.assertFalse(self.channel.email_verified)

    def test_it_handles_twilio_call_status_failed(self):
        r = self.csrf_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)
Esempio n. 7
0
class NotifyTestCase(BaseTestCase):
    def _setup_data(self, kind, 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 = kind
        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("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")
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertEqual(payload["event_type"], "trigger")
        self.assertEqual(payload["service_key"], "123")

    @patch("hc.api.transports.requests.request")
    def test_pd_complex(self, mock_post):
        self._setup_data("pd", json.dumps({"service_key": "456"}))
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertEqual(payload["event_type"], "trigger")
        self.assertEqual(payload["service_key"], "456")

    @patch("hc.api.transports.requests.request")
    def test_pagertree(self, mock_post):
        self._setup_data("pagertree", "123")
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertEqual(payload["event_type"], "trigger")

    @patch("hc.api.transports.requests.request")
    def test_pagerteam(self, mock_post):
        self._setup_data("pagerteam", "123")

        self.channel.notify(self.check)
        self.assertFalse(mock_post.called)
        self.assertEqual(Notification.objects.count(), 0)

    @patch("hc.api.transports.requests.request")
    def test_slack(self, mock_post):
        self._setup_data("slack", "123")
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        attachment = payload["attachments"][0]
        fields = {f["title"]: f["value"] for f in attachment["fields"]}
        self.assertEqual(fields["Last Ping"], "an hour ago")

    @patch("hc.api.transports.requests.request")
    def test_slack_with_complex_value(self, mock_post):
        v = json.dumps({"incoming_webhook": {"url": "123"}})
        self._setup_data("slack", v)
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        self.assertEqual(args[1], "123")

    @patch("hc.api.transports.requests.request")
    def test_slack_handles_500(self, mock_post):
        self._setup_data("slack", "123")
        mock_post.return_value.status_code = 500

        self.channel.notify(self.check)

        n = Notification.objects.get()
        self.assertEqual(n.error, "Received status code 500")

    @patch("hc.api.transports.requests.request", side_effect=Timeout)
    def test_slack_handles_timeout(self, mock_post):
        self._setup_data("slack", "123")

        self.channel.notify(self.check)

        n = Notification.objects.get()
        self.assertEqual(n.error, "Connection timed out")

    @patch("hc.api.transports.requests.request")
    def test_slack_with_tabs_in_schedule(self, mock_post):
        self._setup_data("slack", "123")
        self.check.kind = "cron"
        self.check.schedule = "*\t* * * *"
        self.check.save()
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        self.assertEqual(Notification.objects.count(), 1)
        self.assertTrue(mock_post.called)

    @patch("hc.api.transports.requests.request")
    def test_hipchat(self, mock_post):
        self._setup_data("hipchat", "123")

        self.channel.notify(self.check)
        self.assertFalse(mock_post.called)
        self.assertEqual(Notification.objects.count(), 0)

    @patch("hc.api.transports.requests.request")
    def test_opsgenie_with_legacy_value(self, mock_post):
        self._setup_data("opsgenie", "123")
        mock_post.return_value.status_code = 202

        self.channel.notify(self.check)
        n = Notification.objects.first()
        self.assertEqual(n.error, "")

        self.assertEqual(mock_post.call_count, 1)
        args, kwargs = mock_post.call_args
        self.assertIn("api.opsgenie.com", args[1])
        payload = kwargs["json"]
        self.assertIn("DOWN", payload["message"])

    @patch("hc.api.transports.requests.request")
    def test_opsgenie_up(self, mock_post):
        self._setup_data("opsgenie", "123", status="up")
        mock_post.return_value.status_code = 202

        self.channel.notify(self.check)
        n = Notification.objects.first()
        self.assertEqual(n.error, "")

        self.assertEqual(mock_post.call_count, 1)
        args, kwargs = mock_post.call_args
        method, url = args
        self.assertTrue(str(self.check.code) in url)

    @patch("hc.api.transports.requests.request")
    def test_opsgenie_with_json_value(self, mock_post):
        self._setup_data("opsgenie", json.dumps({
            "key": "456",
            "region": "eu"
        }))
        mock_post.return_value.status_code = 202

        self.channel.notify(self.check)
        n = Notification.objects.first()
        self.assertEqual(n.error, "")

        self.assertEqual(mock_post.call_count, 1)
        args, kwargs = mock_post.call_args
        self.assertIn("api.eu.opsgenie.com", args[1])

    @patch("hc.api.transports.requests.request")
    def test_opsgenie_returns_error(self, mock_post):
        self._setup_data("opsgenie", "123")
        mock_post.return_value.status_code = 403
        mock_post.return_value.json.return_value = {"message": "Nice try"}

        self.channel.notify(self.check)
        n = Notification.objects.first()
        self.assertEqual(
            n.error, 'Received status code 403 with a message: "Nice try"')

    @patch("hc.api.transports.requests.request")
    def test_pushover(self, mock_post):
        self._setup_data("po", "123|0")
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["data"]
        self.assertIn("DOWN", payload["title"])

    @patch("hc.api.transports.requests.request")
    def test_pushover_up_priority(self, mock_post):
        self._setup_data("po", "123|0|2", status="up")
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["data"]
        self.assertIn("UP", payload["title"])
        self.assertEqual(payload["priority"], 2)
        self.assertIn("retry", payload)
        self.assertIn("expire", payload)

    @patch("hc.api.transports.requests.request")
    def test_victorops(self, mock_post):
        self._setup_data("victorops", "123")
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertEqual(payload["message_type"], "CRITICAL")

    @patch("hc.api.transports.requests.request")
    def test_discord(self, mock_post):
        v = json.dumps({"webhook": {"url": "123"}})
        self._setup_data("discord", v)
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        attachment = payload["attachments"][0]
        fields = {f["title"]: f["value"] for f in attachment["fields"]}
        self.assertEqual(fields["Last Ping"], "an hour ago")

    @patch("hc.api.transports.requests.request")
    def test_discord_rewrites_discordapp_com(self, mock_post):
        v = json.dumps({"webhook": {"url": "https://discordapp.com/foo"}})
        self._setup_data("discord", v)
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        url = args[1]

        # discordapp.com is deprecated. For existing webhook URLs, wwe should
        # rewrite discordapp.com to discord.com:
        self.assertEqual(url, "https://discord.com/foo/slack")

    @patch("hc.api.transports.requests.request")
    def test_pushbullet(self, mock_post):
        self._setup_data("pushbullet", "fake-token")
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        _, kwargs = mock_post.call_args
        self.assertEqual(kwargs["json"]["type"], "note")
        self.assertEqual(kwargs["headers"]["Access-Token"], "fake-token")

    @patch("hc.api.transports.requests.request")
    def test_telegram(self, mock_post):
        v = json.dumps({"id": 123})
        self._setup_data("telegram", v)
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertEqual(payload["chat_id"], 123)
        self.assertTrue("The check" in payload["text"])

    @patch("hc.api.transports.requests.request")
    def test_telegram_returns_error(self, mock_post):
        self._setup_data("telegram", json.dumps({"id": 123}))
        mock_post.return_value.status_code = 400
        mock_post.return_value.json.return_value = {"description": "Hi"}

        self.channel.notify(self.check)
        n = Notification.objects.first()
        self.assertEqual(n.error,
                         'Received status code 400 with a message: "Hi"')

    def test_telegram_obeys_rate_limit(self):
        self._setup_data("telegram", json.dumps({"id": 123}))

        TokenBucket.objects.create(value="tg-123", tokens=0)

        self.channel.notify(self.check)
        n = Notification.objects.first()
        self.assertEqual(n.error, "Rate limit exceeded")

    @patch("hc.api.transports.requests.request")
    def test_call(self, mock_post):
        self.profile.call_limit = 1
        self.profile.save()

        value = {"label": "foo", "value": "+1234567890"}
        self._setup_data("call", json.dumps(value))
        self.check.last_ping = now() - td(hours=2)

        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)

        args, kwargs = mock_post.call_args
        payload = kwargs["data"]
        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")
    def test_call_limit(self, mock_post):
        # At limit already:
        self.profile.last_call_date = now()
        self.profile.calls_sent = 50
        self.profile.save()

        definition = {"value": "+1234567890"}
        self._setup_data("call", json.dumps(definition))

        self.channel.notify(self.check)
        self.assertFalse(mock_post.called)

        n = Notification.objects.get()
        self.assertTrue("Monthly phone call limit exceeded" in n.error)

        # And email should have been sent
        self.assertEqual(len(mail.outbox), 1)

        email = mail.outbox[0]
        self.assertEqual(email.to[0], "*****@*****.**")
        self.assertEqual(email.subject, "Monthly Phone Call Limit Reached")

    @patch("hc.api.transports.requests.request")
    def test_call_limit_reset(self, mock_post):
        # At limit, but also into a new month
        self.profile.calls_sent = 50
        self.profile.last_call_date = now() - td(days=100)
        self.profile.save()

        self._setup_data("sms", "+1234567890")
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        self.assertTrue(mock_post.called)

    def test_not_implimented(self):
        self._setup_data("webhook", "http://example")
        self.channel.kind = "invalid"

        with self.assertRaises(NotImplementedError):
            self.channel.notify(self.check)

    @patch("hc.api.transports.requests.request")
    def test_msteams(self, mock_post):
        self._setup_data("msteams", "http://example.com/webhook")
        mock_post.return_value.status_code = 200

        self.check.name = "_underscores_ & more"

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertEqual(payload["@type"], "MessageCard")

        # summary and title should be the same, except
        # title should have any special HTML characters escaped
        self.assertEqual(payload["summary"], "“_underscores_ & more” is DOWN.")
        self.assertEqual(payload["title"],
                         "“_underscores_ & more” is DOWN.")

    @patch("hc.api.transports.requests.request")
    def test_msteams_escapes_html_and_markdown_in_desc(self, mock_post):
        self._setup_data("msteams", "http://example.com/webhook")
        mock_post.return_value.status_code = 200

        self.check.desc = """
            TEST _underscore_ `backticks` <u>underline</u> \\backslash\\ "quoted"
        """

        self.channel.notify(self.check)

        args, kwargs = mock_post.call_args
        text = kwargs["json"]["sections"][0]["text"]

        self.assertIn(r"\_underscore\_", text)
        self.assertIn(r"\`backticks\`", text)
        self.assertIn("&lt;u&gt;underline&lt;/u&gt;", text)
        self.assertIn(r"\\backslash\\ ", text)
        self.assertIn("&quot;quoted&quot;", text)

    @patch("hc.api.transports.os.system")
    @override_settings(SHELL_ENABLED=True)
    def test_shell(self, mock_system):
        definition = {"cmd_down": "logger hello", "cmd_up": ""}
        self._setup_data("shell", json.dumps(definition))
        mock_system.return_value = 0

        self.channel.notify(self.check)
        mock_system.assert_called_with("logger hello")

    @patch("hc.api.transports.os.system")
    @override_settings(SHELL_ENABLED=True)
    def test_shell_handles_nonzero_exit_code(self, mock_system):
        definition = {"cmd_down": "logger hello", "cmd_up": ""}
        self._setup_data("shell", json.dumps(definition))
        mock_system.return_value = 123

        self.channel.notify(self.check)
        n = Notification.objects.get()
        self.assertEqual(n.error, "Command returned exit code 123")

    @patch("hc.api.transports.os.system")
    @override_settings(SHELL_ENABLED=True)
    def test_shell_supports_variables(self, mock_system):
        definition = {
            "cmd_down": "logger $NAME is $STATUS ($TAG1)",
            "cmd_up": ""
        }
        self._setup_data("shell", json.dumps(definition))
        mock_system.return_value = 0

        self.check.name = "Database"
        self.check.tags = "foo bar"
        self.check.save()
        self.channel.notify(self.check)

        mock_system.assert_called_with("logger Database is down (foo)")

    @patch("hc.api.transports.os.system")
    @override_settings(SHELL_ENABLED=False)
    def test_shell_disabled(self, mock_system):
        definition = {"cmd_down": "logger hello", "cmd_up": ""}
        self._setup_data("shell", json.dumps(definition))

        self.channel.notify(self.check)
        self.assertFalse(mock_system.called)

        n = Notification.objects.get()
        self.assertEqual(n.error, "Shell commands are not enabled")
Esempio n. 8
0
class SendTestNotificationTestCase(BaseTestCase):
    def setUp(self):
        super(SendTestNotificationTestCase, self).setUp()
        self.channel = Channel(kind="email", project=self.project)
        self.channel.email_verified = True
        self.channel.value = "*****@*****.**"
        self.channel.save()

        self.url = "/integrations/%s/test/" % self.channel.code

    def test_it_sends_test_email(self):

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, {}, follow=True)
        self.assertRedirects(r, self.channels_url)
        self.assertContains(r, "Test notification sent!")

        # And email should have been sent
        self.assertEqual(len(mail.outbox), 1)

        email = mail.outbox[0]
        self.assertEqual(email.to[0], "*****@*****.**")
        self.assertTrue("X-Status-Url" in email.extra_headers)
        self.assertTrue("List-Unsubscribe" in email.extra_headers)

        # It should create a notification
        n = Notification.objects.get()
        self.assertEqual(n.channel, self.channel)
        self.assertEqual(n.error, "")

    def test_it_clears_channel_last_error(self):
        self.channel.last_error = "Something went wrong"
        self.channel.save()

        self.client.login(username="******", password="******")
        self.client.post(self.url, {})

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.last_error, "")

    def test_it_sets_channel_last_error(self):
        self.channel.email_verified = False
        self.channel.save()

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, {}, follow=True)

        self.assertContains(r, "Could not send a test notification")
        self.assertContains(r, "Email not verified")

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.last_error, "Email not verified")

    @patch("hc.api.transports.requests.request")
    def test_it_handles_webhooks_with_no_down_url(self, mock_get):
        mock_get.return_value.status_code = 200

        self.channel.kind = "webhook"
        self.channel.value = json.dumps({
            "method_down": "GET",
            "url_down": "",
            "body_down": "",
            "headers_down": {},
            "method_up": "GET",
            "url_up": "http://example-url",
            "body_up": "",
            "headers_up": {},
        })
        self.channel.save()

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, {}, follow=True)
        self.assertRedirects(r, self.channels_url)
        self.assertContains(r, "Test notification sent!")

    def test_it_handles_webhooks_with_no_urls(self):
        self.channel.kind = "webhook"
        self.channel.value = json.dumps({
            "method_down": "GET",
            "url_down": "",
            "body_down": "",
            "headers_down": {},
            "method_up": "GET",
            "url_up": "",
            "body_up": "",
            "headers_up": {},
        })
        self.channel.save()

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, {}, follow=True)
        self.assertRedirects(r, self.channels_url)
        self.assertContains(r, "Could not send a test notification")

    def test_it_checks_channel_ownership(self):
        self.client.login(username="******", password="******")
        r = self.client.post(self.url, {}, follow=True)
        self.assertEqual(r.status_code, 404)
class EditWebhookTestCase(BaseTestCase):
    def setUp(self):
        super(EditWebhookTestCase, self).setUp()

        definition = {
            "method_down": "GET",
            "url_down": "http://example.org/down",
            "body_down": "$NAME is down",
            "headers_down": {
                "User-Agent": "My-Custom-UA"
            },
            "method_up": "GET",
            "url_up": "http://example.org/up",
            "body_up": "$NAME is up",
            "headers_up": {},
        }

        self.channel = Channel(project=self.project, kind="webhook")
        self.channel.name = "Call example.org"
        self.channel.value = json.dumps(definition)
        self.channel.save()

        self.url = "/integrations/%s/edit_webhook/" % self.channel.code

    def test_it_shows_form(self):
        self.client.login(username="******", password="******")
        r = self.client.get(self.url)
        self.assertContains(r, "Webhook Settings")

        self.assertContains(r, "Call example.org")

        # down
        self.assertContains(r, "http://example.org/down")
        self.assertContains(r, "My-Custom-UA")
        self.assertContains(r, "$NAME is down")

        # up
        self.assertContains(r, "http://example.org/up")
        self.assertContains(r, "$NAME is up")

    def test_it_saves_form_and_redirects(self):
        form = {
            "name": "Call foo.com / bar.com",
            "method_down": "POST",
            "url_down": "http://foo.com",
            "headers_down": "X-Foo: 1\nX-Bar: 2",
            "body_down": "going down",
            "method_up": "POST",
            "url_up": "https://bar.com",
            "headers_up": "Content-Type: text/plain",
            "body_up": "going up",
        }

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, form)
        self.assertRedirects(r, self.channels_url)

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.name, "Call foo.com / bar.com")

        down_spec = self.channel.down_webhook_spec
        self.assertEqual(down_spec["method"], "POST")
        self.assertEqual(down_spec["url"], "http://foo.com")
        self.assertEqual(down_spec["body"], "going down")
        self.assertEqual(down_spec["headers"], {"X-Foo": "1", "X-Bar": "2"})

        up_spec = self.channel.up_webhook_spec
        self.assertEqual(up_spec["method"], "POST")
        self.assertEqual(up_spec["url"], "https://bar.com")
        self.assertEqual(up_spec["body"], "going up")
        self.assertEqual(up_spec["headers"], {"Content-Type": "text/plain"})

    def test_it_requires_kind_webhook(self):
        self.channel.kind = "email"
        self.channel.value = "*****@*****.**"
        self.channel.save()

        self.client.login(username="******", password="******")
        r = self.client.get(self.url)
        self.assertEqual(r.status_code, 400)

    def test_it_requires_rw_access(self):
        self.bobs_membership.rw = False
        self.bobs_membership.save()

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, {})
        self.assertEqual(r.status_code, 403)
Esempio n. 10
0
class EditEmailTestCase(BaseTestCase):
    def setUp(self):
        super().setUp()

        self.check = Check.objects.create(project=self.project)

        self.channel = Channel(project=self.project, kind="email")
        self.channel.value = json.dumps(
            {"value": "*****@*****.**", "up": True, "down": True}
        )
        self.channel.email_verified = True
        self.channel.save()

        self.url = f"/integrations/{self.channel.code}/edit/"

    def test_it_shows_form(self):
        self.client.login(username="******", password="******")
        r = self.client.get(self.url)
        self.assertContains(r, "Get an email message when check goes up or down.")
        self.assertContains(r, "*****@*****.**")
        self.assertContains(r, "Email Settings")

    def test_it_saves_changes(self):
        form = {"value": "*****@*****.**", "down": "true", "up": "false"}

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, form)
        self.assertRedirects(r, self.channels_url)

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.email_value, "*****@*****.**")
        self.assertTrue(self.channel.email_notify_down)
        self.assertFalse(self.channel.email_notify_up)

        # It should send a verification link
        email = mail.outbox[0]
        self.assertTrue(email.subject.startswith("Verify email address on"))
        self.assertEqual(email.to[0], "*****@*****.**")

        # Make sure it does not call assign_all_checks
        self.assertFalse(self.channel.checks.exists())

    def test_it_skips_verification_if_email_unchanged(self):
        form = {"value": "*****@*****.**", "down": "false", "up": "true"}

        self.client.login(username="******", password="******")
        self.client.post(self.url, form)

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.email_value, "*****@*****.**")
        self.assertFalse(self.channel.email_notify_down)
        self.assertTrue(self.channel.email_notify_up)
        self.assertTrue(self.channel.email_verified)

        # The email address did not change, so we should skip verification
        self.assertEqual(len(mail.outbox), 0)

    def test_team_access_works(self):
        form = {"value": "*****@*****.**", "down": "true", "up": "true"}

        self.client.login(username="******", password="******")
        self.client.post(self.url, form)

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.email_value, "*****@*****.**")

    @override_settings(EMAIL_USE_VERIFICATION=False)
    def test_it_hides_confirmation_needed_notice(self):
        self.client.login(username="******", password="******")
        r = self.client.get(self.url)
        self.assertNotContains(r, "Requires confirmation")

    @override_settings(EMAIL_USE_VERIFICATION=False)
    def test_it_auto_verifies_email(self):
        form = {"value": "*****@*****.**", "down": "true", "up": "true"}

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, form)
        self.assertRedirects(r, self.channels_url)

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.email_value, "*****@*****.**")

        # Email should *not* have been sent
        self.assertEqual(len(mail.outbox), 0)

    def test_it_auto_verifies_own_email(self):
        form = {"value": "*****@*****.**", "down": "true", "up": "true"}

        self.client.login(username="******", password="******")
        r = self.client.post(self.url, form)
        self.assertRedirects(r, self.channels_url)

        self.channel.refresh_from_db()
        self.assertEqual(self.channel.email_value, "*****@*****.**")

        # Email should *not* have been sent
        self.assertEqual(len(mail.outbox), 0)

    def test_it_requires_rw_access(self):
        self.bobs_membership.role = "r"
        self.bobs_membership.save()

        self.client.login(username="******", password="******")
        r = self.client.get(self.url)
        self.assertEqual(r.status_code, 403)
class NotifyTelegramTestCase(BaseTestCase):
    def setUp(self):
        super().setUp()

        self.check = Check(project=self.project)
        self.check.status = "down"
        self.check.last_ping = now() - td(minutes=61)
        self.check.save()

        self.channel = Channel(project=self.project)
        self.channel.kind = "telegram"
        self.channel.value = json.dumps({"id": 123})
        self.channel.save()
        self.channel.checks.add(self.check)

    @patch("hc.api.transports.requests.request")
    def test_it_works(self, mock_post):
        mock_post.return_value.status_code = 200

        self.channel.notify(self.check)
        assert Notification.objects.count() == 1

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertEqual(payload["chat_id"], 123)
        self.assertIn("The check", payload["text"])
        self.assertIn(self.check.cloaked_url(), payload["text"])

        # Only one check in the project, so there should be no note about
        # other checks:
        self.assertNotIn("All the other checks are up.", payload["text"])

    @patch("hc.api.transports.requests.request")
    def test_it_returns_error(self, mock_post):
        mock_post.return_value.status_code = 400
        mock_post.return_value.json.return_value = {"description": "Hi"}

        self.channel.notify(self.check)
        n = Notification.objects.get()
        self.assertEqual(n.error, 'Received status code 400 with a message: "Hi"')

    @patch("hc.api.transports.requests.request")
    def test_it_handles_non_json_error(self, mock_post):
        mock_post.return_value.status_code = 400
        mock_post.return_value.json = Mock(side_effect=ValueError)

        self.channel.notify(self.check)
        n = Notification.objects.get()
        self.assertEqual(n.error, "Received status code 400")

    @patch("hc.api.transports.requests.request")
    def test_it_handles_group_supergroup_migration(self, mock_post):
        error_response = Mock(status_code=400)
        error_response.json.return_value = {
            "description": "Hello",
            "parameters": {"migrate_to_chat_id": -234},
        }

        mock_post.side_effect = [error_response, Mock(status_code=200)]

        self.channel.notify(self.check)
        self.assertEqual(mock_post.call_count, 2)

        # The chat id should have been updated
        self.channel.refresh_from_db()
        self.assertEqual(self.channel.telegram_id, -234)

        # There should be no logged error
        n = Notification.objects.get()
        self.assertEqual(n.error, "")

    def test_telegram_obeys_rate_limit(self):
        TokenBucket.objects.create(value="tg-123", tokens=0)

        self.channel.notify(self.check)
        n = Notification.objects.get()
        self.assertEqual(n.error, "Rate limit exceeded")

    @patch("hc.api.transports.requests.request")
    def test_it_shows_all_other_checks_up_note(self, mock_post):
        mock_post.return_value.status_code = 200

        other = Check(project=self.project)
        other.name = "Foobar"
        other.status = "up"
        other.last_ping = now() - td(minutes=61)
        other.save()

        self.channel.notify(self.check)

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertIn("All the other checks are up.", payload["text"])

    @patch("hc.api.transports.requests.request")
    def test_it_lists_other_down_checks(self, mock_post):
        mock_post.return_value.status_code = 200

        other = Check(project=self.project)
        other.name = "Foobar"
        other.status = "down"
        other.last_ping = now() - td(minutes=61)
        other.save()

        self.channel.notify(self.check)

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertIn("The following checks are also down", payload["text"])
        self.assertIn("Foobar", payload["text"])
        self.assertIn(other.cloaked_url(), payload["text"])

    @patch("hc.api.transports.requests.request")
    def test_it_does_not_show_more_than_10_other_checks(self, mock_post):
        mock_post.return_value.status_code = 200

        for i in range(0, 11):
            other = Check(project=self.project)
            other.name = f"Foobar #{i}"
            other.status = "down"
            other.last_ping = now() - td(minutes=61)
            other.save()

        self.channel.notify(self.check)

        args, kwargs = mock_post.call_args
        payload = kwargs["json"]
        self.assertNotIn("Foobar", payload["text"])
        self.assertIn("11 other checks are also down.", payload["text"])

    @patch("hc.api.transports.requests.request")
    def test_it_disables_channel_on_403_group_deleted(self, mock_post):
        mock_post.return_value.status_code = 403
        mock_post.return_value.json.return_value = {
            "description": "Forbidden: the group chat was deleted"
        }

        self.channel.notify(self.check)
        self.channel.refresh_from_db()
        self.assertTrue(self.channel.disabled)