@ -244,9 +244,21 @@ class NotifyTestCase(BaseTestCase): | |||||
self.assertEqual(len(mail.outbox), 1) | self.assertEqual(len(mail.outbox), 1) | ||||
email = mail.outbox[0] | email = mail.outbox[0] | ||||
self.assertEqual(email.to[0], "[email protected]") | |||||
self.assertTrue("X-Bounce-Url" in email.extra_headers) | self.assertTrue("X-Bounce-Url" in email.extra_headers) | ||||
self.assertTrue("List-Unsubscribe" in email.extra_headers) | self.assertTrue("List-Unsubscribe" in email.extra_headers) | ||||
def test_email_transport_handles_json_value(self): | |||||
payload = {"value": "[email protected]", "up": True, "down": True} | |||||
self._setup_data("email", json.dumps(payload)) | |||||
self.channel.notify(self.check) | |||||
# And email should have been sent | |||||
self.assertEqual(len(mail.outbox), 1) | |||||
email = mail.outbox[0] | |||||
self.assertEqual(email.to[0], "[email protected]") | |||||
def test_it_skips_unverified_email(self): | def test_it_skips_unverified_email(self): | ||||
self._setup_data("email", "[email protected]", email_verified=False) | self._setup_data("email", "[email protected]", email_verified=False) | ||||
self.channel.notify(self.check) | self.channel.notify(self.check) | ||||
@ -256,6 +268,15 @@ class NotifyTestCase(BaseTestCase): | |||||
self.assertEqual(Notification.objects.count(), 0) | self.assertEqual(Notification.objects.count(), 0) | ||||
self.assertEqual(len(mail.outbox), 0) | self.assertEqual(len(mail.outbox), 0) | ||||
def test_email_checks_up_down_flags(self): | |||||
payload = {"value": "[email protected]", "up": True, "down": False} | |||||
self._setup_data("email", json.dumps(payload)) | |||||
self.channel.notify(self.check) | |||||
# This channel should not notify on "down" events: | |||||
self.assertEqual(Notification.objects.count(), 0) | |||||
self.assertEqual(len(mail.outbox), 0) | |||||
@patch("hc.api.transports.requests.request") | @patch("hc.api.transports.requests.request") | ||||
def test_pd(self, mock_post): | def test_pd(self, mock_post): | ||||
self._setup_data("pd", "123") | self._setup_data("pd", "123") | ||||
@ -1,3 +1,5 @@ | |||||
import json | |||||
from django.core import mail | from django.core import mail | ||||
from django.test.utils import override_settings | from django.test.utils import override_settings | ||||
@ -12,7 +14,7 @@ class AddEmailTestCase(BaseTestCase): | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get(self.url) | r = self.client.get(self.url) | ||||
self.assertContains(r, "Get an email message") | self.assertContains(r, "Get an email message") | ||||
self.assertContains(r, "Confirmation needed") | |||||
self.assertContains(r, "Requires confirmation") | |||||
def test_it_creates_channel(self): | def test_it_creates_channel(self): | ||||
form = {"value": "[email protected]"} | form = {"value": "[email protected]"} | ||||
@ -22,8 +24,9 @@ class AddEmailTestCase(BaseTestCase): | |||||
self.assertRedirects(r, "/integrations/") | self.assertRedirects(r, "/integrations/") | ||||
c = Channel.objects.get() | c = Channel.objects.get() | ||||
doc = json.loads(c.value) | |||||
self.assertEqual(c.kind, "email") | self.assertEqual(c.kind, "email") | ||||
self.assertEqual(c.value, "[email protected]") | |||||
self.assertEqual(doc["value"], "[email protected]") | |||||
self.assertFalse(c.email_verified) | self.assertFalse(c.email_verified) | ||||
self.assertEqual(c.project, self.project) | self.assertEqual(c.project, self.project) | ||||
@ -32,6 +35,8 @@ class AddEmailTestCase(BaseTestCase): | |||||
email = mail.outbox[0] | email = mail.outbox[0] | ||||
self.assertTrue(email.subject.startswith("Verify email address on")) | self.assertTrue(email.subject.startswith("Verify email address on")) | ||||
# Make sure we're sending to an email address, not a JSON string: | |||||
self.assertEqual(email.to[0], "[email protected]") | |||||
def test_team_access_works(self): | def test_team_access_works(self): | ||||
form = {"value": "[email protected]"} | form = {"value": "[email protected]"} | ||||
@ -57,13 +62,14 @@ class AddEmailTestCase(BaseTestCase): | |||||
self.client.post(self.url, form) | self.client.post(self.url, form) | ||||
c = Channel.objects.get() | c = Channel.objects.get() | ||||
self.assertEqual(c.value, "[email protected]") | |||||
doc = json.loads(c.value) | |||||
self.assertEqual(doc["value"], "[email protected]") | |||||
@override_settings(EMAIL_USE_VERIFICATION=False) | @override_settings(EMAIL_USE_VERIFICATION=False) | ||||
def test_it_hides_confirmation_needed_notice(self): | def test_it_hides_confirmation_needed_notice(self): | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get(self.url) | r = self.client.get(self.url) | ||||
self.assertNotContains(r, "Confirmation needed") | |||||
self.assertNotContains(r, "Requires confirmation") | |||||
@override_settings(EMAIL_USE_VERIFICATION=False) | @override_settings(EMAIL_USE_VERIFICATION=False) | ||||
def test_it_auto_verifies_email(self): | def test_it_auto_verifies_email(self): | ||||
@ -74,8 +80,9 @@ class AddEmailTestCase(BaseTestCase): | |||||
self.assertRedirects(r, "/integrations/") | self.assertRedirects(r, "/integrations/") | ||||
c = Channel.objects.get() | c = Channel.objects.get() | ||||
doc = json.loads(c.value) | |||||
self.assertEqual(c.kind, "email") | self.assertEqual(c.kind, "email") | ||||
self.assertEqual(c.value, "[email protected]") | |||||
self.assertEqual(doc["value"], "[email protected]") | |||||
self.assertTrue(c.email_verified) | self.assertTrue(c.email_verified) | ||||
# Email should *not* have been sent | # Email should *not* have been sent | ||||
@ -74,6 +74,34 @@ class ChannelsTestCase(BaseTestCase): | |||||
self.assertEqual(r.status_code, 200) | self.assertEqual(r.status_code, 200) | ||||
self.assertContains(r, "Unconfirmed") | self.assertContains(r, "Unconfirmed") | ||||
def test_it_shows_down_only_note_for_email(self): | |||||
channel = Channel(project=self.project, kind="email") | |||||
channel.value = json.dumps({ | |||||
"value": "[email protected]", | |||||
"up": False, | |||||
"down": True | |||||
}) | |||||
channel.save() | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get("/integrations/") | |||||
self.assertEqual(r.status_code, 200) | |||||
self.assertContains(r, "(down only)") | |||||
def test_it_shows_up_only_note_for_email(self): | |||||
channel = Channel(project=self.project, kind="email") | |||||
channel.value = json.dumps({ | |||||
"value": "[email protected]", | |||||
"up": True, | |||||
"down": False | |||||
}) | |||||
channel.save() | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get("/integrations/") | |||||
self.assertEqual(r.status_code, 200) | |||||
self.assertContains(r, "(up only)") | |||||
def test_it_shows_sms_label(self): | def test_it_shows_sms_label(self): | ||||
ch = Channel(kind="sms", project=self.project) | ch = Channel(kind="sms", project=self.project) | ||||
ch.value = json.dumps({"value": "+123", "label": "My Phone"}) | ch.value = json.dumps({"value": "+123", "label": "My Phone"}) | ||||
@ -1,3 +1,5 @@ | |||||
import json | |||||
from hc.api.models import Channel, Check, Notification, Ping | from hc.api.models import Channel, Check, Notification, Ping | ||||
from hc.test import BaseTestCase | from hc.test import BaseTestCase | ||||
@ -15,20 +17,20 @@ class LogTestCase(BaseTestCase): | |||||
ping.created = "2000-01-01T00:00:00+00:00" | ping.created = "2000-01-01T00:00:00+00:00" | ||||
ping.save() | ping.save() | ||||
self.url = "/checks/%s/log/" % self.check.code | |||||
def test_it_works(self): | def test_it_works(self): | ||||
url = "/checks/%s/log/" % self.check.code | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get(url) | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "Local Time", status_code=200) | self.assertContains(r, "Local Time", status_code=200) | ||||
def test_team_access_works(self): | def test_team_access_works(self): | ||||
url = "/checks/%s/log/" % self.check.code | |||||
# Logging in as bob, not alice. Bob has team access so this | # Logging in as bob, not alice. Bob has team access so this | ||||
# should work. | # should work. | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get(url) | |||||
r = self.client.get(self.url) | |||||
self.assertEqual(r.status_code, 200) | self.assertEqual(r.status_code, 200) | ||||
def test_it_handles_bad_uuid(self): | def test_it_handles_bad_uuid(self): | ||||
@ -47,40 +49,50 @@ class LogTestCase(BaseTestCase): | |||||
self.assertEqual(r.status_code, 404) | self.assertEqual(r.status_code, 404) | ||||
def test_it_checks_ownership(self): | def test_it_checks_ownership(self): | ||||
url = "/checks/%s/log/" % self.check.code | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get(url) | |||||
r = self.client.get(self.url) | |||||
self.assertEqual(r.status_code, 404) | self.assertEqual(r.status_code, 404) | ||||
def test_it_shows_pushover_notifications(self): | |||||
ch = Channel.objects.create(kind="po", project=self.project) | |||||
def test_it_shows_email_notification(self): | |||||
ch = Channel(kind="email", project=self.project) | |||||
ch.value = json.dumps({ | |||||
"value": "[email protected]", | |||||
"up": True, | |||||
"down": True | |||||
}) | |||||
ch.save() | |||||
Notification(owner=self.check, channel=ch, check_status="down").save() | Notification(owner=self.check, channel=ch, check_status="down").save() | ||||
url = "/checks/%s/log/" % self.check.code | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "Sent email alert to [email protected]", | |||||
status_code=200) | |||||
def test_it_shows_pushover_notification(self): | |||||
ch = Channel.objects.create(kind="po", project=self.project) | |||||
Notification(owner=self.check, channel=ch, check_status="down").save() | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get(url) | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "Sent a Pushover notification", status_code=200) | self.assertContains(r, "Sent a Pushover notification", status_code=200) | ||||
def test_it_shows_webhook_notifications(self): | |||||
def test_it_shows_webhook_notification(self): | |||||
ch = Channel(kind="webhook", project=self.project) | ch = Channel(kind="webhook", project=self.project) | ||||
ch.value = "foo/$NAME" | ch.value = "foo/$NAME" | ||||
ch.save() | ch.save() | ||||
Notification(owner=self.check, channel=ch, check_status="down").save() | Notification(owner=self.check, channel=ch, check_status="down").save() | ||||
url = "/checks/%s/log/" % self.check.code | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get(url) | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "Called webhook foo/$NAME", status_code=200) | self.assertContains(r, "Called webhook foo/$NAME", status_code=200) | ||||
def test_it_allows_cross_team_access(self): | def test_it_allows_cross_team_access(self): | ||||
self.bobs_profile.current_project = None | self.bobs_profile.current_project = None | ||||
self.bobs_profile.save() | self.bobs_profile.save() | ||||
url = "/checks/%s/log/" % self.check.code | |||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get(url) | |||||
r = self.client.get(self.url) | |||||
self.assertEqual(r.status_code, 200) | self.assertEqual(r.status_code, 200) |
@ -1,26 +1,20 @@ | |||||
{% extends "base.html" %} | {% extends "base.html" %} | ||||
{% load humanize static hc_extras %} | {% load humanize static hc_extras %} | ||||
{% block title %}Notification Channels - {% site_name %}{% endblock %} | |||||
{% block title %}Set Up Email Notifications - {% site_name %}{% endblock %} | |||||
{% block content %} | {% block content %} | ||||
<div class="row"> | <div class="row"> | ||||
<div class="col-sm-12"> | <div class="col-sm-12"> | ||||
<h1>Email</h1> | <h1>Email</h1> | ||||
<p>Get an email message when check goes up or down.</p> | <p>Get an email message when check goes up or down.</p> | ||||
<p> | |||||
<strong>Tip:</strong> | |||||
Add multiple email addresses, to notify multiple team members. | |||||
</p> | |||||
{% if use_verification %} | {% if use_verification %} | ||||
<p> | |||||
<strong>Confirmation needed.</strong> | |||||
<p class="alert alert-info"> | |||||
<strong>Requires confirmation.</strong> | |||||
After entering an email address, {% site_name %} will send out a confirmation link. | After entering an email address, {% site_name %} will send out a confirmation link. | ||||
Only confirmed addresses will receive notifications. | |||||
Only confirmed addresses receive notifications. | |||||
</p> | </p> | ||||
{% endif %} | {% endif %} | ||||
@ -37,6 +31,7 @@ | |||||
class="form-control" | class="form-control" | ||||
name="value" | name="value" | ||||
placeholder="[email protected]" | placeholder="[email protected]" | ||||
required | |||||
value="{{ form.value.value|default:"" }}"> | value="{{ form.value.value|default:"" }}"> | ||||
{% if form.value.errors %} | {% if form.value.errors %} | ||||
@ -46,6 +41,36 @@ | |||||
{% endif %} | {% endif %} | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div id="add-email-notify-group" class="form-group"> | |||||
<label class="col-sm-2 control-label">Notify When</label> | |||||
<div class="col-sm-10"> | |||||
<label class="checkbox-container"> | |||||
<input | |||||
type="checkbox" | |||||
name="down" | |||||
value="true" | |||||
{% if form.down.value %} checked {% endif %}> | |||||
<span class="checkmark"></span> | |||||
A check goes <strong>down</strong> | |||||
</label> | |||||
<label class="checkbox-container"> | |||||
<input | |||||
type="checkbox" | |||||
name="up" | |||||
value="true" | |||||
{% if form.up.value %} checked {% endif %}> | |||||
<span class="checkmark"></span> | |||||
A check goes <strong>up</strong> | |||||
<br /> | |||||
<span class="text-muted"> | |||||
Ticketing system integrations: untick this and avoid creating new tickets | |||||
when checks come back up. | |||||
</span> | |||||
</label> | |||||
</div> | |||||
</div> | |||||
<div class="form-group"> | <div class="form-group"> | ||||
<div class="col-sm-offset-2 col-sm-10"> | <div class="col-sm-offset-2 col-sm-10"> | ||||
<button type="submit" class="btn btn-primary">Save Integration</button> | <button type="submit" class="btn btn-primary">Save Integration</button> | ||||