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)
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)
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)
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"], "½")
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)
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("<u>underline</u>", text) self.assertIn(r"\\backslash\\ ", text) self.assertIn(""quoted"", 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")
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)
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)