class UpdateNameTestCase(TestCase): def setUp(self): self.alice = User(username="******") self.alice.set_password("password") self.alice.save() self.check = Check(user=self.alice) self.check.save() def test_it_works(self): url = "/checks/%s/name/" % self.check.code payload = {"name": "Alice Was Here"} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 302 check = Check.objects.get(code=self.check.code) assert check.name == "Alice Was Here" def test_it_checks_ownership(self): charlie = User(username="******") charlie.set_password("password") charlie.save() url = "/checks/%s/name/" % self.check.code payload = {"name": "Charlie Sent This"} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 403 def test_it_handles_bad_uuid(self): url = "/checks/not-uuid/name/" payload = {"name": "Alice Was Here"} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 400 def test_it_handles_missing_uuid(self): # Valid UUID but there is no check for it: url = "/checks/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/name/" payload = {"name": "Alice Was Here"} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 404 def test_it_sanitizes_tags(self): url = "/checks/%s/name/" % self.check.code payload = {"tags": " foo bar\r\t \n baz \n"} self.client.login(username="******", password="******") self.client.post(url, data=payload) check = Check.objects.get(id=self.check.id) self.assertEqual(check.tags, "foo bar baz")
def test_post_works(self): check = Check() check.save() csrf_client = Client(enforce_csrf_checks=True) r = csrf_client.post("/ping/%s/" % check.code) assert r.status_code == 200
def test_it_works(self): check = Check() check.save() payload = [{ "event": "inbound", "msg": { "raw_msg": "This is raw message", "to": [ ["*****@*****.**", "Somebody"], ["*****@*****.**" % check.code, "Healthchecks"] ] } }] data = {"mandrill_events": json.dumps(payload)} r = self.client.post("/handle_email/", data=data) assert r.status_code == 200 same_check = Check.objects.get(code=check.code) assert same_check.status == "up" pings = list(Ping.objects.all()) assert pings[0].scheme == "email" assert pings[0].body == "This is raw message"
def test_status_works_with_grace_period(self): check = Check() check.status = "up" check.last_ping = timezone.now() - timedelta(days=1, minutes=30) self.assertTrue(check.in_grace_period()) self.assertEqual(check.get_status(), "up")
def checks(request): if request.method == "GET": q = Check.objects.filter(user=request.user) tags = set(request.GET.getlist("tag")) for tag in tags: # approximate filtering by tags q = q.filter(tags__contains=tag) checks = [] for check in q: # precise, final filtering if not tags or check.matches_tag_set(tags): checks.append(check.to_dict()) return JsonResponse({"checks": checks}) elif request.method == "POST": created = False check = _lookup(request.user, request.json) if check is None: num_checks = Check.objects.filter(user=request.user).count() if num_checks >= request.user.profile.check_limit: return HttpResponseForbidden() check = Check(user=request.user) created = True _update(check, request.json) return JsonResponse(check.to_dict(), status=201 if created else 200) # If request is neither GET nor POST, return "405 Method not allowed" return HttpResponse(status=405)
def checks(request): if request.method == "GET": q = Check.objects.filter(user=request.user) doc = {"checks": [check.to_dict() for check in q]} return JsonResponse(doc) elif request.method == "POST": check = Check(user=request.user) check.name = str(request.json.get("name", "")) check.tags = str(request.json.get("tags", "")) if "timeout" in request.json: check.timeout = td(seconds=request.json["timeout"]) if "grace" in request.json: check.grace = td(seconds=request.json["grace"]) check.save() # This needs to be done after saving the check, because of # the M2M relation between checks and channels: if request.json.get("channels") == "*": check.assign_all_channels() return JsonResponse(check.to_dict(), status=201) # If request is neither GET nor POST, return "405 Method not allowed" return HttpResponse(status=405)
def test_head_works(self): check = Check() check.save() csrf_client = Client(enforce_csrf_checks=True) r = csrf_client.head("/ping/%s/" % check.code) assert r.status_code == 200 assert Ping.objects.count() == 1
def test_it_strips_tags(self): check = Check() check.tags = " foo bar " self.assertEquals(check.tags_list(), ["foo", "bar"]) check.tags = " " self.assertEquals(check.tags_list(), [])
def test_it_handles_grace_period(self): check = Check(user=self.alice, status="up") # 1 day 30 minutes after ping the check is in grace period: check.last_ping = timezone.now() - timedelta(days=1, minutes=30) check.save() # Expect no exceptions-- handle_one(check)
def test_it_validates_ownership(self): check = Check(user=self.bob, status="up") check.save() url = "/api/v1/checks/%s/pause" % check.code r = self.client.post(url, "", content_type="application/json", HTTP_X_API_KEY="abc") self.assertEqual(r.status_code, 400)
def add_check(request): assert request.method == "POST" check = Check(user=request.team.user) check.save() check.assign_all_channels() return redirect("hc-checks")
def test_it_shows_only_users_checks(self): bobs_check = Check(user=self.bob, name="Bob 1") bobs_check.save() r = self.get() data = r.json() self.assertEqual(len(data["checks"]), 2) for check in data["checks"]: self.assertNotEqual(check["name"], "Bob 1")
def test_unique_accepts_only_whitelisted_values(self): existing = Check(user=self.alice, name="Foo") existing.save() self.post({ "api_key": "abc", "name": "Foo", "unique": ["status"] }, expected_fragment="unexpected value")
class NotifyTestCase(TestCase): def _setup_data(self, channel_kind, channel_value, email_verified=True): self.alice = User(username="******") self.alice.save() self.check = Check() self.check.status = "down" self.check.save() self.channel = Channel(user=self.alice) self.channel.kind = channel_kind self.channel.value = channel_value self.channel.email_verified = email_verified self.channel.save() self.channel.checks.add(self.check) @patch("hc.api.models.requests.get") def test_webhook(self, mock_get): self._setup_data("webhook", "http://example") mock_get.return_value.status_code = 200 self.channel.notify(self.check) mock_get.assert_called_with(u"http://example", timeout=5) @patch("hc.api.models.requests.get", side_effect=ReadTimeout) def test_webhooks_handle_timeouts(self, mock_get): self._setup_data("webhook", "http://example") self.channel.notify(self.check) assert Notification.objects.count() == 1 def test_email(self): self._setup_data("email", "*****@*****.**") self.channel.notify(self.check) assert Notification.objects.count() == 1 # And email should have been sent self.assertEqual(len(mail.outbox), 1) def test_it_skips_unverified_email(self): self._setup_data("email", "*****@*****.**", email_verified=False) self.channel.notify(self.check) assert Notification.objects.count() == 0 self.assertEqual(len(mail.outbox), 0) @patch("hc.api.models.requests.post") 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 assert "trigger" in kwargs["data"]
def test_it_switches(self): c = Check(user=self.alice, name="This belongs to Alice") c.save() self.client.login(username="******", password="******") url = "/accounts/switch_team/%s/" % self.alice.username r = self.client.get(url, follow=True) self.assertContains(r, "This belongs to Alice")
def test_it_works(self): check = Check(user=self.alice) check.last_ping_body = "this is body" check.save() Ping.objects.create(owner=check) self.client.login(username="******", password="******") r = self.client.post("/checks/%s/last_ping/" % check.code) self.assertContains(r, "this is body", status_code=200)
class UpdateTimeoutTestCase(BaseTestCase): def setUp(self): super(UpdateTimeoutTestCase, self).setUp() self.check = Check(user=self.alice) self.check.save() def test_it_works(self): url = "/checks/%s/timeout/" % self.check.code payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) self.assertRedirects(r, "/checks/") check = Check.objects.get(code=self.check.code) assert check.timeout.total_seconds() == 3600 assert check.grace.total_seconds() == 60 def test_team_access_works(self): url = "/checks/%s/timeout/" % self.check.code payload = {"timeout": 7200, "grace": 60} # Logging in as bob, not alice. Bob has team access so this # should work. self.client.login(username="******", password="******") self.client.post(url, data=payload) check = Check.objects.get(code=self.check.code) assert check.timeout.total_seconds() == 7200 def test_it_handles_bad_uuid(self): url = "/checks/not-uuid/timeout/" payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 400 def test_it_handles_missing_uuid(self): # Valid UUID but there is no check for it: url = "/checks/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/timeout/" payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 404 def test_it_checks_ownership(self): url = "/checks/%s/timeout/" % self.check.code payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 403
class MyChecksTestCase(BaseTestCase): def setUp(self): super(MyChecksTestCase, self).setUp() self.check = Check(user=self.alice, name="Alice Was Here") self.check.save() def test_it_works(self): self.client.login(username="******", password="******") r = self.client.get("/checks/") self.assertContains(r, "Alice Was Here", status_code=200)
def test_it_truncates_long_ua(self): ua = "01234567890" * 30 check = Check() check.save() r = self.client.get("/ping/%s/" % check.code, HTTP_USER_AGENT=ua) assert r.status_code == 200 pings = list(Ping.objects.all()) assert len(pings[0].ua) == 200 assert ua.startswith(pings[0].ua)
def add_check(request): num_checks = Check.objects.filter(user=request.team.user).count() if num_checks >= request.team.check_limit: return HttpResponseBadRequest() check = Check(user=request.team.user) check.save() check.assign_all_channels() return redirect("hc-checks")
def test_it_works(self): check = Check() check.save() r = self.client.get("/ping/%s/" % check.code) assert r.status_code == 200 same_check = Check.objects.get(code=check.code) assert same_check.status == "up" pings = list(Ping.objects.all()) assert pings[0].scheme == "http"
def test_it_works(self): check = Check(user=self.alice, status="up") check.save() url = "/api/v1/checks/%s/pause" % check.code r = self.client.post(url, "", content_type="application/json", HTTP_X_API_KEY="abc") self.assertEqual(r.status_code, 200) check.refresh_from_db() self.assertEqual(check.status, "paused")
def test_it_handles_120_char_ua(self): ua = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/44.0.2403.89 Safari/537.36") check = Check() check.save() r = self.client.get("/ping/%s/" % check.code, HTTP_USER_AGENT=ua) assert r.status_code == 200 pings = list(Ping.objects.all()) assert pings[0].ua == ua
def test_it_skips_nag_if_none_down(self): check = Check(name="Test Check", user=self.alice) check.last_ping = now() check.save() self.profile.nag_period = td(hours=1) self.profile.save() sent = self.profile.send_report(nag=True) self.assertFalse(sent) self.assertEqual(len(mail.outbox), 0)
def test_it_sends_report(self): check = Check(name="Test Check", user=self.alice) check.save() self.alice.profile.send_report() # And an email should have been sent self.assertEqual(len(mail.outbox), 1) message = mail.outbox[0] self.assertEqual(message.subject, 'Monthly Report') self.assertIn("Test Check", message.body)
def setUp(self): super(ListChecksTestCase, self).setUp() self.a1 = Check(user=self.alice, name="Alice 1") self.a1.timeout = td(seconds=3600) self.a1.grace = td(seconds=900) self.a1.save() self.a2 = Check(user=self.alice, name="Alice 2") self.a2.timeout = td(seconds=86400) self.a2.grace = td(seconds=3600) self.a2.save()
def _welcome_check(request): check = None if "welcome_code" in request.session: code = request.session["welcome_code"] check = Check.objects.filter(code=code).first() if check is None: check = Check() check.save() request.session["welcome_code"] = str(check.code) return check
class RemoveCheckTestCase(BaseTestCase): def setUp(self): super(RemoveCheckTestCase, self).setUp() self.check = Check(user=self.alice) self.check.save() def test_it_works(self): url = "/checks/%s/remove/" % self.check.code self.client.login(username="******", password="******") r = self.client.post(url) self.assertRedirects(r, "/checks/") assert Check.objects.count() == 0 def test_team_access_works(self): url = "/checks/%s/remove/" % self.check.code # Logging in as bob, not alice. Bob has team access so this # should work. self.client.login(username="******", password="******") self.client.post(url) assert Check.objects.count() == 0 def test_it_handles_bad_uuid(self): url = "/checks/not-uuid/remove/" self.client.login(username="******", password="******") r = self.client.post(url) assert r.status_code == 400 def test_it_checks_owner(self): url = "/checks/%s/remove/" % self.check.code self.client.login(username="******", password="******") r = self.client.post(url) assert r.status_code == 403 def test_it_handles_missing_uuid(self): # Valid UUID but there is no check for it: url = "/checks/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/remove/" self.client.login(username="******", password="******") r = self.client.post(url) assert r.status_code == 404 def test_it_rejects_get(self): url = "/checks/%s/remove/" % self.check.code self.client.login(username="******", password="******") r = self.client.get(url) self.assertEqual(r.status_code, 405)
class UpdateTimeoutTestCase(TestCase): def setUp(self): self.alice = User(username="******") self.alice.set_password("password") self.alice.save() self.check = Check(user=self.alice) self.check.save() def test_it_works(self): url = "/checks/%s/timeout/" % self.check.code payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 302 check = Check.objects.get(code=self.check.code) assert check.timeout.total_seconds() == 3600 assert check.grace.total_seconds() == 60 def test_it_handles_bad_uuid(self): url = "/checks/not-uuid/timeout/" payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 400 def test_it_handles_missing_uuid(self): # Valid UUID but there is no check for it: url = "/checks/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/timeout/" payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 404 def test_it_checks_ownership(self): charlie = User(username="******") charlie.set_password("password") charlie.save() url = "/checks/%s/timeout/" % self.check.code payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 403
class MyChecksTestCase(TestCase): def setUp(self): self.alice = User(username="******") self.alice.set_password("password") self.alice.save() self.check = Check(user=self.alice, name="Alice Was Here") self.check.save() def test_it_works(self): self.client.login(username="******", password="******") r = self.client.get("/checks/") self.assertContains(r, "Alice Was Here", status_code=200)
def test_it_works_synchronously(self, mock_notify): check = Check(project=self.project, status="up") check.last_ping = now() - td(days=2) check.alert_after = check.last_ping + td(days=1, hours=1) check.save() call_command("sendalerts", loop=False, use_threads=False, stdout=StringIO()) # It should call `notify` instead of `notify_on_thread` self.assertTrue(mock_notify.called)
def test_it_works(self): check = Check(user=self.alice, status="up") check.save() url = "/api/v1/checks/%s/pause" % check.code r = self.client.post(url, "", content_type="application/json", HTTP_X_API_KEY="X" * 32) self.assertEqual(r.status_code, 200) self.assertEqual(r["Access-Control-Allow-Origin"], "*") check.refresh_from_db() self.assertEqual(check.status, "paused")
class StatusSingleTestCase(BaseTestCase): def setUp(self): super(StatusSingleTestCase, self).setUp() self.check = Check(project=self.project, name="Alice Was Here") self.check.save() def test_it_works(self): self.client.login(username="******", password="******") r = self.client.get("/checks/%s/status/" % self.check.code) doc = r.json() self.assertEqual(doc["status"], "new") self.assertTrue("never received a ping" in doc["status_text"]) self.assertTrue("not received any pings yet" in doc["events"]) def test_it_returns_events(self): p = Ping.objects.create(owner=self.check, ua="test-user-agent", n=1) self.check.status = "up" self.check.last_ping = p.created self.check.save() self.client.login(username="******", password="******") r = self.client.get("/checks/%s/status/" % self.check.code) doc = r.json() self.assertEqual(doc["status"], "up") self.assertEqual(doc["updated"], p.created.strftime("%s.%f")) self.assertTrue("test-user-agent" in doc["events"]) def test_it_omits_events(self): p = Ping.objects.create(owner=self.check, ua="test-user-agent", n=1) self.check.status = "up" self.check.last_ping = p.created self.check.save() timestamp = p.created.strftime("%s.%f") url = "/checks/%s/status/?u=%s" % (self.check.code, timestamp) self.client.login(username="******", password="******") r = self.client.get(url) doc = r.json() self.assertFalse("events" in doc) def test_it_allows_cross_team_access(self): self.bobs_profile.current_project = None self.bobs_profile.save() self.client.login(username="******", password="******") r = self.client.get("/checks/%s/status/" % self.check.code) self.assertEqual(r.status_code, 200)
def test_it_does_not_show_more_than_10_other_checks(self, mock_post): self._setup_data("123|0") 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["data"] self.assertNotIn("Foobar", payload["message"]) self.assertIn("11 other checks are also down.", payload["message"])
def test_status_works_with_cron_syntax(self): dt = timezone.make_aware(datetime(2000, 1, 1), timezone=timezone.utc) # Expect ping every midnight, default grace is 1 hour check = Check() check.kind = "cron" check.schedule = "0 0 * * *" check.status = "up" check.last_ping = dt with patch("hc.api.models.now") as mock_now: # 23:59pm mock_now.return_value = dt + td(hours=23, minutes=59) self.assertEqual(check.get_status(), "up") with patch("hc.api.models.now") as mock_now: # 00:00am mock_now.return_value = dt + td(days=1) self.assertEqual(check.get_status(), "grace") with patch("hc.api.models.now") as mock_now: # 1:30am mock_now.return_value = dt + td(days=1, minutes=60) self.assertEqual(check.get_status(), "down")
class PauseTestCase(BaseTestCase): def setUp(self): super(PauseTestCase, self).setUp() self.check = Check(user=self.alice, status="up") self.check.save() self.url = "/checks/%s/pause/" % self.check.code def test_it_pauses(self): self.client.login(username="******", password="******") r = self.client.post(self.url) self.assertRedirects(r, "/checks/") self.check.refresh_from_db() self.assertEqual(self.check.status, "paused") 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_allows_cross_team_access(self): self.bobs_profile.current_team = None self.bobs_profile.save() self.client.login(username="******", password="******") r = self.client.post(self.url) self.assertRedirects(r, "/checks/") def test_it_clears_last_start_alert_after(self): self.check.last_start = now() self.check.alert_after = self.check.last_start + td(hours=1) self.check.save() self.client.login(username="******", password="******") self.client.post(self.url) self.check.refresh_from_db() self.assertEqual(self.check.last_start, None) self.assertEqual(self.check.alert_after, None)
def test_it_creates_a_flip_when_check_goes_down(self): check = Check(project=self.project, status="up") check.last_ping = now() - td(days=2) check.alert_after = check.last_ping + td(days=1, hours=1) check.save() result = Command().handle_going_down() # If it finds work, it should return True self.assertTrue(result) # It should create a flip object flip = Flip.objects.get() self.assertEqual(flip.owner_id, check.id) self.assertEqual(flip.created, check.alert_after) self.assertEqual(flip.new_status, "down") # It should change stored status to "down", and clear out alert_after check.refresh_from_db() self.assertEqual(check.status, "down") self.assertEqual(check.alert_after, None)
def test_it_updates_members_next_nag_date(self): self.bobs_profile.nag_period = timedelta(hours=1) self.bobs_profile.save() check = Check(user=self.alice, status="down") check.last_ping = now() - timedelta(days=2) check.alert_after = check.get_alert_after() check.save() flip = Flip(owner=check, created=check.last_ping) flip.old_status = "up" flip.new_status = "down" flip.save() notify(flip.id, Mock()) self.bobs_profile.refresh_from_db() self.assertIsNotNone(self.bobs_profile.next_nag_date)
def test_it_works(self): check = Check(user=self.alice, status="up") check.save() url = "/api/v1/checks/%s/pause" % check.code r = self.client.post(url, "", content_type="application/json", HTTP_X_API_KEY="abc") # Assert the expected status code and check's status self.assertEqual(r.status_code, 200) # Assert the new database value of status is paused check.refresh_from_db() self.assertEqual(check.status, "paused")
def test_it_accepts_n(self): check = Check(user=self.alice) check.save() # remote_addr, scheme, method, ua, body: check.ping("1.2.3.4", "http", "post", "tester", "foo-123") check.ping("1.2.3.4", "http", "post", "tester", "bar-456") self.client.login(username="******", password="******") r = self.client.get("/checks/%s/pings/1/" % check.code) self.assertContains(r, "foo-123", status_code=200) r = self.client.get("/checks/%s/pings/2/" % check.code) self.assertContains(r, "bar-456", status_code=200)
def test_it_does_not_touch_already_set_next_nag_dates(self): original_nag_date = now() - timedelta(minutes=30) self.profile.nag_period = timedelta(hours=1) self.profile.next_nag_date = original_nag_date self.profile.save() check = Check(user=self.alice, status="down") check.last_ping = now() - timedelta(days=2) check.alert_after = check.get_alert_after() check.save() flip = Flip(owner=check, created=check.last_ping) flip.old_status = "up" flip.new_status = "down" flip.save() notify(flip.id, Mock()) self.profile.refresh_from_db() self.assertEqual(self.profile.next_nag_date, original_nag_date)
def test_it_handles_few(self, mock): yesterday = timezone.now() - timedelta(days=1) names = ["Check %d" % d for d in range(0,10)] for name in names: check = Check(user=self.alice, name=name) check.alert_after = yesterday check.status = "up" check.save() result = Command().handle_many() self.assertEqual(result, True) handled_names = [] for args, kwargs in mock.call_args_list: handled_names.append(args[0].name) self.assertEqual(len(names), len(handled_names))
def test_channel_assignment(self): check = Check() check.status = "up" check.user = self.alice check.save() channel = Channel(user=self.alice) channel.save() channel.checks.add(check) count_before = channel.checks.count() resp = self.post({ "api_key": "abc", "channels": "*" }) count_after = channel.checks.count() self.assertEqual((count_after - count_before), 1)
def test_downtimes_handles_months_when_check_did_not_exist(self): check = Check(project=self.project) check.created = datetime(2020, 1, 1, tzinfo=timezone.utc) check.save() nov, dec, jan = check.downtimes(3) # Nov. 2019 self.assertIsNone(nov[1]) self.assertIsNone(nov[2]) # Dec. 2019 self.assertIsNone(dec[1]) self.assertIsNone(dec[2]) # Jan. 2020 self.assertEqual(jan[1], td()) self.assertEqual(jan[2], 0)
def test_it_sends_nag(self): check = Check(name="Test Check", user=self.alice) check.status = "down" check.last_ping = now() check.save() self.profile.nag_period = td(hours=1) self.profile.save() sent = self.profile.send_report(nag=True) self.assertTrue(sent) # And an email should have been sent self.assertEqual(len(mail.outbox), 1) message = mail.outbox[0] self.assertEqual(message.subject, 'Reminder: 1 check still down') self.assertIn("Test Check", message.body)
def test_it_processes_flip(self, mock_notify): check = Check(user=self.alice, status="up") check.last_ping = now() check.alert_after = check.get_alert_after() check.save() flip = Flip(owner=check, created=check.last_ping) flip.old_status = "down" flip.new_status = "up" flip.save() result = Command().process_one_flip() # If it finds work, it should return True self.assertTrue(result) # It should set the processed date flip.refresh_from_db() self.assertTrue(flip.processed) # It should call `notify_on_thread` self.assertTrue(mock_notify.called)
def create_checks_in_db(self, checks: [dict], project_id: int): checks_to_create = [] for check in checks: checks_to_create.append( Check( name=check['name'], tags=check['tags'], desc=check['desc'], grace=td(seconds=check['grace']), n_pings=check['n_pings'], last_ping=check['last_ping'], timeout=td(seconds=check['timeout']) if 'timeout' in check else DEFAULT_TIMEOUT, tz=check['tz'] if 'tz' in check else 'UTC', schedule=check['schedule'] if 'schedule' in check else "* * * * *", code=check['ping_url'][check['ping_url'].find('.com/') + 5:], kind='simple' if 'timeout' in check else 'cron', project_id=project_id, )) Check.objects.bulk_create(checks_to_create)
def test_it_handles_few(self, mock): yesterday = timezone.now() - timedelta(days=1) names = ["Check %d" % d for d in range(0, 10)] for name in names: check = Check(user=self.alice, name=name) check.alert_after = yesterday check.status = "up" check.save() ### Assert when Command's handle many that when handle_many should return True result = Command().handle_many() assert result, "handle_many should return True" handled_names = [] for args, kwargs in mock.call_args_list: handled_names.append(args[0].name) assert set(names) == set(handled_names)
def test_it_handles_few(self, mock): alice = User(username="******") alice.save() names = ["Check %d" % d for d in range(0, 10)] for name in names: check = Check(user=alice, name=name) check.alert_after = datetime(2000, 1, 1) check.status = "up" check.save() result = handle_many() assert result, "handle_many should return True" handled_names = [] for args, kwargs in mock.call_args_list: handled_names.append(args[0].name) assert set(names) == set(handled_names)
def test_downtimes_handles_no_flips(self): check = Check(project=self.project) check.created = datetime(2019, 1, 1, tzinfo=timezone.utc) check.save() nov, dec, jan = check.downtimes(3) # Nov. 2019 self.assertEqual(nov[0].strftime("%m-%Y"), "11-2019") self.assertEqual(nov[1], td()) self.assertEqual(nov[2], 0) # Dec. 2019 self.assertEqual(dec[0].strftime("%m-%Y"), "12-2019") self.assertEqual(dec[1], td()) self.assertEqual(dec[2], 0) # Jan. 2020 self.assertEqual(jan[0].strftime("%m-%Y"), "01-2020") self.assertEqual(jan[1], td()) self.assertEqual(jan[2], 0)
class ListChecksTestCase(BaseTestCase): def setUp(self): super(ListChecksTestCase, self).setUp() self.now = now().replace(microsecond=0) self.a1 = Check(user=self.alice, name="Alice 1") self.a1.timeout = td(seconds=3600) self.a1.grace = td(seconds=900) self.a1.last_ping = self.now self.a1.n_pings = 1 self.a1.status = "new" self.a1.tags = "a1-tag a1-additional-tag" self.a1.save() self.a2 = Check(user=self.alice, name="Alice 2") self.a2.timeout = td(seconds=86400) self.a2.grace = td(seconds=3600) self.a2.last_ping = self.now self.a2.status = "up" self.a2.tags = "a2-tag" self.a2.save() self.c1 = Channel.objects.create(user=self.alice) self.a1.channel_set.add(self.c1) def get(self): return self.client.get("/api/v1/checks/", HTTP_X_API_KEY="X" * 32) def test_it_works(self): r = self.get() self.assertEqual(r.status_code, 200) doc = r.json() self.assertEqual(len(doc["checks"]), 2) a1 = None a2 = None for check in doc["checks"]: if check["name"] == "Alice 1": a1 = check if check["name"] == "Alice 2": a2 = check self.assertEqual(a1["timeout"], 3600) self.assertEqual(a1["grace"], 900) self.assertEqual(a1["ping_url"], self.a1.url()) self.assertEqual(a1["last_ping"], self.now.isoformat()) self.assertEqual(a1["n_pings"], 1) self.assertEqual(a1["status"], "new") self.assertEqual(a1["channels"], str(self.c1.code)) update_url = settings.SITE_ROOT + "/api/v1/checks/%s" % self.a1.code pause_url = update_url + "/pause" self.assertEqual(a1["update_url"], update_url) self.assertEqual(a1["pause_url"], pause_url) next_ping = self.now + td(seconds=3600) self.assertEqual(a1["next_ping"], next_ping.isoformat()) self.assertEqual(a2["timeout"], 86400) self.assertEqual(a2["grace"], 3600) self.assertEqual(a2["ping_url"], self.a2.url()) self.assertEqual(a2["status"], "up") def test_it_shows_only_users_checks(self): bobs_check = Check(user=self.bob, name="Bob 1") bobs_check.save() r = self.get() data = r.json() self.assertEqual(len(data["checks"]), 2) for check in data["checks"]: self.assertNotEqual(check["name"], "Bob 1") def test_it_accepts_api_key_from_request_body(self): payload = json.dumps({"api_key": "X" * 32}) r = self.client.generic("GET", "/api/v1/checks/", payload, content_type="application/json") self.assertEqual(r.status_code, 200) self.assertContains(r, "Alice") def test_it_works_with_tags_param(self): r = self.client.get("/api/v1/checks/?tag=a2-tag", HTTP_X_API_KEY="X" * 32) self.assertEqual(r.status_code, 200) doc = r.json() self.assertTrue("checks" in doc) self.assertEqual(len(doc["checks"]), 1) check = doc["checks"][0] self.assertEqual(check["name"], "Alice 2") self.assertEqual(check["tags"], "a2-tag") def test_it_filters_with_multiple_tags_param(self): r = self.client.get("/api/v1/checks/?tag=a1-tag&tag=a1-additional-tag", HTTP_X_API_KEY="X" * 32) self.assertEqual(r.status_code, 200) doc = r.json() self.assertTrue("checks" in doc) self.assertEqual(len(doc["checks"]), 1) check = doc["checks"][0] self.assertEqual(check["name"], "Alice 1") self.assertEqual(check["tags"], "a1-tag a1-additional-tag") def test_it_does_not_match_tag_partially(self): r = self.client.get("/api/v1/checks/?tag=tag", HTTP_X_API_KEY="X" * 32) self.assertEqual(r.status_code, 200) doc = r.json() self.assertTrue("checks" in doc) self.assertEqual(len(doc["checks"]), 0) def test_non_existing_tags_filter_returns_empty_result(self): r = self.client.get( "/api/v1/checks/?tag=non_existing_tag_with_no_checks", HTTP_X_API_KEY="X" * 32) self.assertEqual(r.status_code, 200) doc = r.json() self.assertTrue("checks" in doc) self.assertEqual(len(doc["checks"]), 0) def test_readonly_key_works(self): self.profile.api_key_readonly = "R" * 32 self.profile.save() r = self.client.get("/api/v1/checks/", HTTP_X_API_KEY="R" * 32) self.assertEqual(r.status_code, 200)
class NotifyTestCase(BaseTestCase): def _setup_data(self, kind, value, status="down", email_verified=True): self.check = Check() self.check.status = status self.check.user = self.alice self.check.save() self.channel = Channel(user=self.alice) 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): self._setup_data("webhook", "http://example") mock_get.return_value.status_code = 200 self.channel.notify(self.check) mock_get.assert_called_with("get", u"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): self._setup_data("webhook", "http://example") self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "Connection timed out") @patch("hc.api.transports.requests.request") def test_webhooks_ignore_up_events(self, mock_get): self._setup_data("webhook", "http://example", 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_support_variables(self, mock_get): template = "http://host/$CODE/$STATUS/$TAG1/$TAG2/?name=$NAME" self._setup_data("webhook", template) self.check.name = "Hello World" self.check.tags = "foo bar" self.check.save() self.channel.notify(self.check) url = u"http://host/%s/down/foo/bar/?name=Hello%%20World" \ % self.check.code mock_get.assert_called_with("get", url, headers={"User-Agent": "healthchecks.io"}, timeout=5) @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: template = "http://host/$NAME" self._setup_data("webhook", template) self.check.name = "$TAG1" self.check.tags = "foo" self.check.save() self.channel.notify(self.check) url = u"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_webhook_fires_on_up_event(self, mock_get): self._setup_data("webhook", "http://foo\nhttp://bar", status="up") self.channel.notify(self.check) mock_get.assert_called_with("get", "http://bar", headers={"User-Agent": "healthchecks.io"}, timeout=5) def test_email(self): self._setup_data("email", "*****@*****.**") self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "") # And email should have been sent self.assertEqual(len(mail.outbox), 1) def test_it_skips_unverified_email(self): self._setup_data("email", "*****@*****.**", email_verified=False) self.channel.notify(self.check) assert Notification.objects.count() == 1 n = Notification.objects.first() self.assertEqual(n.error, "Email not verified") self.assertEqual(len(mail.outbox), 0) @override_settings(USE_PAYMENTS=True) def test_email_contains_upgrade_notice(self): self._setup_data("email", "*****@*****.**", status="up") self.profile.team_access_allowed = False self.profile.save() self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "") # Check is up, payments are enabled, and the user does not have team # access: the email should contain upgrade note message = mail.outbox[0] html, _ = message.alternatives[0] assert "/pricing/" in html @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 json = kwargs["json"] self.assertEqual(json["event_type"], "trigger") @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 json = kwargs["json"] attachment = json["attachments"][0] fields = {f["title"]: f["value"] for f in attachment["fields"]} self.assertEqual(fields["Last Ping"], "Never") @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_hipchat(self, mock_post): self._setup_data("hipchat", "123") mock_post.return_value.status_code = 204 self.channel.notify(self.check) n = Notification.objects.first() self.assertEqual(n.error, "") args, kwargs = mock_post.call_args json = kwargs["json"] self.assertIn("DOWN", json["message"]) @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 json = kwargs["data"] self.assertIn("DOWN", json["title"]) @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 json = kwargs["json"] self.assertEqual(json["message_type"], "CRITICAL") ### Test that the web hooks handle connection errors and error 500s @patch("hc.api.transports.requests.request") def test_webhook(self, mock_get): self._setup_data("webhook", "http://example") mock_get.return_value.status_code = 500 self.channel.notify(self.check) all_notifications = Notification.objects.get() self.assertEqual(all_notifications.error, 'Received status code 500') @patch("hc.api.transports.requests.request", side_effect=Timeout) def test_connection_time_out(self, mock_get): self._setup_data("webhook", "http://example") self.channel.notify(self.check) all_notifications = Notification.objects.get() self.assertEqual(all_notifications.error, "Connection timed out") @patch("hc.api.transports.requests.request", side_effect=ConnectionError) def test_connection_error(self, mock_get): self._setup_data("webhook", "http://example") self.channel.notify(self.check) all_notifications = Notification.objects.get() self.assertEqual(all_notifications.error, "Connection failed")
def setUp(self): super(DeleteCheckTestCase, self).setUp() self.check = Check(user=self.alice) self.check.save()
class NotifyTestCase(BaseTestCase): def _setup_data(self, kind, value, status="down", email_verified=True): self.check = Check() self.check.status = status self.check.user = self.alice self.check.last_ping = now() - td(minutes=61) self.check.save() self.channel = Channel(user=self.alice) 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): self._setup_data("webhook", "http://example") mock_get.return_value.status_code = 200 self.channel.notify(self.check) mock_get.assert_called_with("get", u"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): self._setup_data("webhook", "http://example") self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "Connection timed out") @patch("hc.api.transports.requests.request", side_effect=ConnectionError) def test_webhooks_handle_connection_errors(self, mock_get): self._setup_data("webhook", "http://example") self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "Connection failed") @patch("hc.api.transports.requests.request") def test_webhooks_ignore_up_events(self, mock_get): self._setup_data("webhook", "http://example", 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_500(self, mock_get): self._setup_data("webhook", "http://example") mock_get.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") def test_webhooks_support_variables(self, mock_get): template = "http://host/$CODE/$STATUS/$TAG1/$TAG2/?name=$NAME" self._setup_data("webhook", template) self.check.name = "Hello World" self.check.tags = "foo bar" self.check.save() self.channel.notify(self.check) url = u"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_support_post(self, mock_request): template = "http://example.com\n\nThe Time Is $NOW" self._setup_data("webhook", template) 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("utf-8") 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: template = "http://host/$NAME" self._setup_data("webhook", template) self.check.name = "$TAG1" self.check.tags = "foo" self.check.save() self.channel.notify(self.check) url = u"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_webhook_fires_on_up_event(self, mock_get): self._setup_data("webhook", "http://foo\nhttp://bar", 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_unicode_post_body(self, mock_request): template = u"http://example.com\n\n(╯°□°)╯︵ ┻━┻" self._setup_data("webhook", template) self.check.save() self.channel.notify(self.check) args, kwargs = mock_request.call_args # unicode should be encoded into utf-8 self.assertTrue(isinstance(kwargs["data"], binary_type)) @patch("hc.api.transports.requests.request") def test_webhooks_handle_json_value(self, mock_request): definition = {"url_down": "http://foo.com"} self._setup_data("webhook", json.dumps(definition)) self.channel.notify(self.check) headers = {"User-Agent": "healthchecks.io"} mock_request.assert_called_with("get", "http://foo.com", headers=headers, timeout=5) @patch("hc.api.transports.requests.request") def test_webhooks_handle_json_up_event(self, mock_request): definition = {"url_up": "http://bar"} self._setup_data("webhook", json.dumps(definition), status="up") self.channel.notify(self.check) headers = {"User-Agent": "healthchecks.io"} mock_request.assert_called_with("get", "http://bar", headers=headers, timeout=5) @patch("hc.api.transports.requests.request") def test_webhooks_handle_post_headers(self, mock_request): definition = { "url_down": "http://foo.com", "post_data": "data", "headers": { "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 = { "url_down": "http://foo.com", "headers": { "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 = { "url_down": "http://foo.com", "headers": { "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 = { "url_down": "http://foo.com", "headers": { "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) def test_email(self): self._setup_data("email", "*****@*****.**") self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "") # And email should have been sent self.assertEqual(len(mail.outbox), 1) email = mail.outbox[0] self.assertTrue("X-Bounce-Url" in email.extra_headers) def test_it_skips_unverified_email(self): self._setup_data("email", "*****@*****.**", email_verified=False) self.channel.notify(self.check) # If an email is not verified, it should be skipped over # without logging a notification: self.assertEqual(Notification.objects.count(), 0) self.assertEqual(len(mail.outbox), 0) @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_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") mock_post.return_value.status_code = 204 self.channel.notify(self.check) n = Notification.objects.first() self.assertEqual(n.error, "") args, kwargs = mock_post.call_args payload = kwargs["json"] self.assertIn("DOWN", payload["message"]) @patch("hc.api.transports.requests.request") def test_opsgenie(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 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_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_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_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_sms(self, mock_post): self._setup_data("sms", "+1234567890") self.check.last_ping = now() - td(hours=2) 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.assertEqual(payload["To"], "+1234567890") self.assertFalse(u"\xa0" in payload["Body"]) # sent SMS counter should go up self.profile.refresh_from_db() self.assertEqual(self.profile.sms_sent, 1) @patch("hc.api.transports.requests.request") def test_sms_handles_json_value(self, mock_post): value = {"label": "foo", "value": "+1234567890"} self._setup_data("sms", json.dumps(value)) self.check.last_ping = now() - td(hours=2) 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.assertEqual(payload["To"], "+1234567890") @patch("hc.api.transports.requests.request") def test_sms_limit(self, mock_post): # At limit already: self.profile.last_sms_date = now() self.profile.sms_sent = 50 self.profile.save() self._setup_data("sms", "+1234567890") self.channel.notify(self.check) self.assertFalse(mock_post.called) n = Notification.objects.get() self.assertTrue("Monthly SMS limit exceeded" in n.error) @patch("hc.api.transports.requests.request") def test_sms_limit_reset(self, mock_post): # At limit, but also into a new month self.profile.sms_sent = 50 self.profile.last_sms_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) @patch("hc.api.transports.requests.request") def test_zendesk_down(self, mock_post): v = json.dumps({"access_token": "fake-token", "subdomain": "foo"}) self._setup_data("zendesk", v) mock_post.return_value.status_code = 200 self.channel.notify(self.check) assert Notification.objects.count() == 1 args, kwargs = mock_post.call_args method, url = args self.assertEqual(method, "post") self.assertTrue("foo.zendesk.com" in url) payload = kwargs["json"] self.assertEqual(payload["request"]["type"], "incident") self.assertTrue("down" in payload["request"]["subject"]) headers = kwargs["headers"] self.assertEqual(headers["Authorization"], "Bearer fake-token") @patch("hc.api.transports.requests.request") @patch("hc.api.transports.requests.get") def test_zendesk_up(self, mock_get, mock_post): v = json.dumps({"access_token": "fake-token", "subdomain": "foo"}) self._setup_data("zendesk", v, status="up") mock_post.return_value.status_code = 200 mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = { "requests": [{ "url": "https://foo.example.org/comment", "description": "code is %s" % self.check.code }] } self.channel.notify(self.check) assert Notification.objects.count() == 1 args, kwargs = mock_post.call_args self.assertTrue("foo.example.org" in args[1]) payload = kwargs["json"] self.assertEqual(payload["request"]["type"], "incident") self.assertTrue("UP" in payload["request"]["subject"]) headers = kwargs["headers"] self.assertEqual(headers["Authorization"], "Bearer fake-token") @patch("hc.api.transports.requests.request") @patch("hc.api.transports.requests.get") def test_zendesk_up_with_no_existing_ticket(self, mock_get, mock_post): v = json.dumps({"access_token": "fake-token", "subdomain": "foo"}) self._setup_data("zendesk", v, status="up") mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = {"requests": []} self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "Could not find a ticket to update") self.assertFalse(mock_post.called)
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_zulip(self, mock_post): definition = { "bot_email": "*****@*****.**", "api_key": "fake-key", "mtype": "stream", "to": "general", } self._setup_data("zulip", json.dumps(definition)) mock_post.return_value.status_code = 200 self.channel.notify(self.check) assert Notification.objects.count() == 1 args, kwargs = mock_post.call_args method, url = args self.assertEqual(url, "https://example.org/api/v1/messages") payload = kwargs["data"] self.assertIn("DOWN", payload["topic"]) @patch("hc.api.transports.requests.request") def test_zulip_returns_error(self, mock_post): definition = { "bot_email": "*****@*****.**", "api_key": "fake-key", "mtype": "stream", "to": "general", } self._setup_data("zulip", json.dumps(definition)) mock_post.return_value.status_code = 403 mock_post.return_value.json.return_value = {"msg": "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_zulip_uses_site_parameter(self, mock_post): definition = { "bot_email": "*****@*****.**", "site": "https://custom.example.org", "api_key": "fake-key", "mtype": "stream", "to": "general", } self._setup_data("zulip", json.dumps(definition)) mock_post.return_value.status_code = 200 self.channel.notify(self.check) assert Notification.objects.count() == 1 args, kwargs = mock_post.call_args method, url = args self.assertEqual(url, "https://custom.example.org/api/v1/messages") payload = kwargs["data"] self.assertIn("DOWN", payload["topic"])
def test_it_sends_report(self): check = Check(name="Test Check", user=self.alice) check.save() self.alice.profile.send_report()
def setUp(self): super(PauseTestCase, self).setUp() self.check = Check(user=self.alice, status="up") self.check.save()
class NotifyTestCase(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 = "slack" 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_slack(self, mock_post): self._setup_data("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") uncloak_url = "/cloaked/%s/" % self.check.unique_key self.assertTrue(attachment["title_link"].endswith(uncloak_url)) @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(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("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("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("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) @override_settings(SLACK_ENABLED=False) def test_it_requires_slack_enabled(self): self._setup_data("123") self.channel.notify(self.check) n = Notification.objects.get() self.assertEqual(n.error, "Slack notifications are not enabled.")
class UpdateTimeoutTestCase(BaseTestCase): def setUp(self): super(UpdateTimeoutTestCase, self).setUp() self.check = Check(project=self.project, status="up") self.check.last_ping = timezone.now() self.check.save() self.url = "/checks/%s/timeout/" % self.check.code def test_it_works(self): payload = {"kind": "simple", "timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(self.url, data=payload) self.assertRedirects(r, "/checks/") self.check.refresh_from_db() self.assertEqual(self.check.kind, "simple") self.assertEqual(self.check.timeout.total_seconds(), 3600) self.assertEqual(self.check.grace.total_seconds(), 60) # alert_after should be updated too expected_aa = self.check.last_ping + td(seconds=3600 + 60) self.assertEqual(self.check.alert_after, expected_aa) def test_it_does_not_update_status(self): self.check.last_ping = timezone.now() - td(days=2) self.check.status = "down" self.check.save() # 1 week: payload = {"kind": "simple", "timeout": 3600 * 24 * 7, "grace": 60} self.client.login(username="******", password="******") self.client.post(self.url, data=payload) self.check.refresh_from_db() self.assertEqual(self.check.status, "down") def test_it_saves_cron_expression(self): payload = { "kind": "cron", "schedule": "5 * * * *", "tz": "UTC", "grace": 60 } self.client.login(username="******", password="******") r = self.client.post(self.url, data=payload) self.assertRedirects(r, "/checks/") self.check.refresh_from_db() self.assertEqual(self.check.kind, "cron") self.assertEqual(self.check.schedule, "5 * * * *") def test_it_validates_cron_expression(self): self.client.login(username="******", password="******") samples = ["* invalid *", "1,2 3,* * * *"] for sample in samples: payload = { "kind": "cron", "schedule": sample, "tz": "UTC", "grace": 60 } r = self.client.post(self.url, data=payload) self.assertEqual(r.status_code, 400) # Check should still have its original data: self.check.refresh_from_db() self.assertEqual(self.check.kind, "simple") def test_it_rejects_six_field_cron_expression(self): payload = { "kind": "cron", "schedule": "* * * * * *", # six fields instead of five "tz": "UTC", "grace": 60 } self.client.login(username="******", password="******") r = self.client.post(self.url, data=payload) self.assertEqual(r.status_code, 400) # Check should still have its original data: self.check.refresh_from_db() self.assertEqual(self.check.kind, "simple") def test_it_validates_tz(self): payload = { "kind": "cron", "schedule": "* * * * *", "tz": "not-a-tz", "grace": 60 } self.client.login(username="******", password="******") r = self.client.post(self.url, data=payload) self.assertEqual(r.status_code, 400) # Check should still have its original data: self.check.refresh_from_db() self.assertEqual(self.check.kind, "simple") def test_it_rejects_missing_schedule(self): # tz field is omitted so this should fail: payload = { "kind": "cron", "grace": 60, "tz": "UTC" } self.client.login(username="******", password="******") r = self.client.post(self.url, data=payload) self.assertEqual(r.status_code, 400) def test_it_rejects_missing_tz(self): # tz field is omitted so this should fail: payload = { "kind": "cron", "schedule": "* * * * *", "grace": 60 } self.client.login(username="******", password="******") r = self.client.post(self.url, data=payload) self.assertEqual(r.status_code, 400) def test_team_access_works(self): payload = {"kind": "simple", "timeout": 7200, "grace": 60} # 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) check = Check.objects.get(code=self.check.code) assert check.timeout.total_seconds() == 7200 def test_it_handles_bad_uuid(self): url = "/checks/not-uuid/timeout/" payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(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 = "/checks/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/timeout/" payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(url, data=payload) assert r.status_code == 404 def test_it_checks_ownership(self): payload = {"timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(self.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_allows_cross_team_access(self): self.bobs_profile.current_project = None self.bobs_profile.save() payload = {"kind": "simple", "timeout": 3600, "grace": 60} self.client.login(username="******", password="******") r = self.client.post(self.url, data=payload) self.assertRedirects(r, "/checks/")