@ -0,0 +1,82 @@ | |||
# coding: utf-8 | |||
from datetime import timedelta as td | |||
import json | |||
from unittest.mock import patch | |||
from django.utils.timezone import now | |||
from django.test.utils import override_settings | |||
from hc.api.models import Channel, Check, Notification | |||
from hc.test import BaseTestCase | |||
@override_settings(SIGNAL_CLI_USERNAME="+987654321") | |||
class NotifySignalTestCase(BaseTestCase): | |||
def setUp(self): | |||
super().setUp() | |||
self.check = Check(project=self.project) | |||
self.check.name = "Daily Backup" | |||
self.check.status = "down" | |||
self.check.last_ping = now() - td(minutes=61) | |||
self.check.save() | |||
payload = {"value": "+123456789", "up": True, "down": True} | |||
self.channel = Channel(project=self.project) | |||
self.channel.kind = "signal" | |||
self.channel.value = json.dumps(payload) | |||
self.channel.save() | |||
self.channel.checks.add(self.check) | |||
@patch("hc.api.transports.subprocess.run") | |||
def test_it_works(self, mock_run): | |||
mock_run.return_value.returncode = 0 | |||
self.channel.notify(self.check) | |||
n = Notification.objects.get() | |||
self.assertEqual(n.error, "") | |||
self.assertTrue(mock_run.called) | |||
args, kwargs = mock_run.call_args | |||
cmd = " ".join(args[0]) | |||
self.assertIn("-u +987654321", cmd) | |||
self.assertIn("send +123456789", cmd) | |||
@patch("hc.api.transports.subprocess.run") | |||
def test_it_obeys_down_flag(self, mock_run): | |||
payload = {"value": "+123456789", "up": True, "down": False} | |||
self.channel.value = json.dumps(payload) | |||
self.channel.save() | |||
self.channel.notify(self.check) | |||
# This channel should not notify on "down" events: | |||
self.assertEqual(Notification.objects.count(), 0) | |||
self.assertFalse(mock_run.called) | |||
@patch("hc.api.transports.subprocess.run") | |||
def test_it_requires_signal_cli_username(self, mock_run): | |||
with override_settings(SIGNAL_CLI_USERNAME=None): | |||
self.channel.notify(self.check) | |||
n = Notification.objects.get() | |||
self.assertEqual(n.error, "Signal notifications are not enabled") | |||
self.assertFalse(mock_run.called) | |||
@patch("hc.api.transports.subprocess.run") | |||
def test_it_does_not_escape_special_characters(self, mock_run): | |||
self.check.name = "Foo & Bar" | |||
self.check.save() | |||
mock_run.return_value.returncode = 0 | |||
self.channel.notify(self.check) | |||
self.assertTrue(mock_run.called) | |||
args, kwargs = mock_run.call_args | |||
cmd = " ".join(args[0]) | |||
self.assertIn("Foo & Bar", cmd) |
@ -0,0 +1,60 @@ | |||
from django.test.utils import override_settings | |||
from hc.api.models import Channel | |||
from hc.test import BaseTestCase | |||
@override_settings(SIGNAL_CLI_USERNAME="+123456789") | |||
class AddSignalTestCase(BaseTestCase): | |||
def setUp(self): | |||
super().setUp() | |||
self.url = "/projects/%s/add_signal/" % self.project.code | |||
def test_instructions_work(self): | |||
self.client.login(username="[email protected]", password="password") | |||
r = self.client.get(self.url) | |||
self.assertContains(r, "Get a Signal message") | |||
def test_it_creates_channel(self): | |||
form = { | |||
"label": "My Phone", | |||
"value": "+1234567890", | |||
"down": "true", | |||
"up": "true", | |||
} | |||
self.client.login(username="[email protected]", password="password") | |||
r = self.client.post(self.url, form) | |||
self.assertRedirects(r, self.channels_url) | |||
c = Channel.objects.get() | |||
self.assertEqual(c.kind, "signal") | |||
self.assertEqual(c.phone_number, "+1234567890") | |||
self.assertEqual(c.name, "My Phone") | |||
self.assertTrue(c.signal_notify_down) | |||
self.assertTrue(c.signal_notify_up) | |||
self.assertEqual(c.project, self.project) | |||
def test_it_obeys_up_down_flags(self): | |||
form = {"label": "My Phone", "value": "+1234567890"} | |||
self.client.login(username="[email protected]", password="password") | |||
r = self.client.post(self.url, form) | |||
self.assertRedirects(r, self.channels_url) | |||
c = Channel.objects.get() | |||
self.assertFalse(c.signal_notify_down) | |||
self.assertFalse(c.signal_notify_up) | |||
@override_settings(SIGNAL_CLI_USERNAME=None) | |||
def test_it_handles_unset_username(self): | |||
self.client.login(username="[email protected]", password="password") | |||
r = self.client.get(self.url) | |||
self.assertEqual(r.status_code, 404) | |||
def test_it_requires_rw_access(self): | |||
self.bobs_membership.rw = False | |||
self.bobs_membership.save() | |||
self.client.login(username="[email protected]", password="password") | |||
r = self.client.get(self.url) | |||
self.assertEqual(r.status_code, 403) |
@ -0,0 +1,98 @@ | |||
{% extends "base.html" %} | |||
{% load humanize static hc_extras %} | |||
{% block title %}Add Signal Integration - {{ site_name }}{% endblock %} | |||
{% block content %} | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<h1>Signal</h1> | |||
<p>Get a Signal message when a check goes up or down.</p> | |||
<h2>Integration Settings</h2> | |||
<form method="post" class="form-horizontal"> | |||
{% csrf_token %} | |||
<div class="form-group {{ form.label.css_classes }}"> | |||
<label for="id_label" class="col-sm-2 control-label">Label</label> | |||
<div class="col-sm-6"> | |||
<input | |||
id="id_label" | |||
type="text" | |||
class="form-control" | |||
name="label" | |||
placeholder="Alice's Phone" | |||
value="{{ form.label.value|default:"" }}"> | |||
{% if form.label.errors %} | |||
<div class="help-block"> | |||
{{ form.label.errors|join:"" }} | |||
</div> | |||
{% else %} | |||
<span class="help-block"> | |||
Optional. If you add multiple phone numbers, | |||
the labels will help you tell them apart. | |||
</span> | |||
{% endif %} | |||
</div> | |||
</div> | |||
<div class="form-group {{ form.value.css_classes }}"> | |||
<label for="id_number" class="col-sm-2 control-label">Phone Number</label> | |||
<div class="col-sm-3"> | |||
<input | |||
id="id_number" | |||
type="tel" | |||
class="form-control" | |||
name="value" | |||
placeholder="+1234567890" | |||
value="{{ form.value.value|default:"" }}"> | |||
{% if form.value.errors %} | |||
<div class="help-block"> | |||
{{ form.value.errors|join:"" }} | |||
</div> | |||
{% else %} | |||
<span class="help-block"> | |||
Make sure the phone number starts with "+" and has the | |||
country code. | |||
</span> | |||
{% endif %} | |||
</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> | |||
</label> | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<div class="col-sm-offset-2 col-sm-10"> | |||
<button type="submit" class="btn btn-primary">Save Integration</button> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
{% endblock %} |
@ -0,0 +1,7 @@ | |||
{% load humanize %}{% spaceless %} | |||
{% if check.status == "down" %} | |||
The check “{{ check.name_then_code|safe }}” is DOWN. Last ping was {{ check.last_ping|naturaltime }}. | |||
{% else %} | |||
The check “{{ check.name_then_code|safe }}” is now UP. | |||
{% endif %} | |||
{% endspaceless %} |