@ -24,14 +24,14 @@ class LoginTestCase(BaseTestCase): | |||||
def test_it_sends_link_with_next(self): | def test_it_sends_link_with_next(self): | ||||
form = {"identity": "[email protected]"} | form = {"identity": "[email protected]"} | ||||
r = self.client.post("/accounts/login/?next=/integrations/add_slack/", form) | |||||
r = self.client.post("/accounts/login/?next=" + self.channels_url, form) | |||||
self.assertRedirects(r, "/accounts/login_link_sent/") | self.assertRedirects(r, "/accounts/login_link_sent/") | ||||
self.assertIn("auto-login", r.cookies) | self.assertIn("auto-login", r.cookies) | ||||
# The check_token link should have a ?next= query parameter: | # The check_token link should have a ?next= query parameter: | ||||
self.assertEqual(len(mail.outbox), 1) | self.assertEqual(len(mail.outbox), 1) | ||||
body = mail.outbox[0].body | body = mail.outbox[0].body | ||||
self.assertTrue("/?next=/integrations/add_slack/" in body) | |||||
self.assertTrue("/?next=" + self.channels_url in body) | |||||
@override_settings(SECRET_KEY="test-secret") | @override_settings(SECRET_KEY="test-secret") | ||||
def test_it_rate_limits_emails(self): | def test_it_rate_limits_emails(self): | ||||
@ -85,7 +85,7 @@ class LoginTestCase(BaseTestCase): | |||||
form = {"action": "login", "email": "[email protected]", "password": "password"} | form = {"action": "login", "email": "[email protected]", "password": "password"} | ||||
samples = ["/integrations/add_slack/", "/checks/%s/details/" % check.code] | |||||
samples = [self.channels_url, "/checks/%s/details/" % check.code] | |||||
for s in samples: | for s in samples: | ||||
r = self.client.post("/accounts/login/?next=%s" % s, form) | r = self.client.post("/accounts/login/?next=%s" % s, form) | ||||
@ -1,33 +1,32 @@ | |||||
from django.test.utils import override_settings | |||||
from hc.api.models import Channel | from hc.api.models import Channel | ||||
from hc.test import BaseTestCase | from hc.test import BaseTestCase | ||||
class AddSlackTestCase(BaseTestCase): | class AddSlackTestCase(BaseTestCase): | ||||
@override_settings(SLACK_CLIENT_ID=None) | |||||
def setUp(self): | |||||
super(AddSlackTestCase, self).setUp() | |||||
self.url = "/projects/%s/add_slack/" % self.project.code | |||||
def test_instructions_work(self): | def test_instructions_work(self): | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get("/integrations/add_slack/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "Integration Settings", status_code=200) | self.assertContains(r, "Integration Settings", status_code=200) | ||||
@override_settings(SLACK_CLIENT_ID=None) | |||||
def test_it_works(self): | def test_it_works(self): | ||||
form = {"value": "http://example.org"} | form = {"value": "http://example.org"} | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post("/integrations/add_slack/", form) | |||||
self.assertRedirects(r, "/integrations/") | |||||
r = self.client.post(self.url, form) | |||||
self.assertRedirects(r, self.channels_url) | |||||
c = Channel.objects.get() | c = Channel.objects.get() | ||||
self.assertEqual(c.kind, "slack") | self.assertEqual(c.kind, "slack") | ||||
self.assertEqual(c.value, "http://example.org") | self.assertEqual(c.value, "http://example.org") | ||||
self.assertEqual(c.project, self.project) | self.assertEqual(c.project, self.project) | ||||
@override_settings(SLACK_CLIENT_ID=None) | |||||
def test_it_rejects_bad_url(self): | def test_it_rejects_bad_url(self): | ||||
form = {"value": "not an URL"} | form = {"value": "not an URL"} | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.post("/integrations/add_slack/", form) | |||||
r = self.client.post(self.url, form) | |||||
self.assertContains(r, "Enter a valid URL") | self.assertContains(r, "Enter a valid URL") |
@ -1,84 +1,22 @@ | |||||
import json | |||||
from django.test.utils import override_settings | from django.test.utils import override_settings | ||||
from hc.api.models import Channel | |||||
from hc.test import BaseTestCase | from hc.test import BaseTestCase | ||||
from mock import patch | |||||
@override_settings(SLACK_CLIENT_ID="fake-client-id") | |||||
class AddSlackBtnTestCase(BaseTestCase): | class AddSlackBtnTestCase(BaseTestCase): | ||||
@override_settings(SLACK_CLIENT_ID="foo") | |||||
def test_it_prepares_login_link(self): | |||||
r = self.client.get("/integrations/add_slack/") | |||||
self.assertContains(r, "Before adding Slack integration", status_code=200) | |||||
def setUp(self): | |||||
super(AddSlackBtnTestCase, self).setUp() | |||||
self.url = "/projects/%s/add_slack_btn/" % self.project.code | |||||
self.assertContains(r, "?next=/integrations/add_slack/") | |||||
def test_instructions_work(self): | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "Setup Guide", status_code=200) | |||||
@override_settings(SLACK_CLIENT_ID="foo") | |||||
def test_slack_button(self): | def test_slack_button(self): | ||||
self.client.login(username="[email protected]", password="password") | self.client.login(username="[email protected]", password="password") | ||||
r = self.client.get("/integrations/add_slack/") | |||||
r = self.client.get(self.url) | |||||
self.assertContains(r, "slack.com/oauth/authorize", status_code=200) | self.assertContains(r, "slack.com/oauth/authorize", status_code=200) | ||||
# There should now be a key in session | # There should now be a key in session | ||||
self.assertTrue("slack" in self.client.session) | |||||
@patch("hc.front.views.requests.post") | |||||
def test_it_handles_oauth_response(self, mock_post): | |||||
session = self.client.session | |||||
session["slack"] = "foo" | |||||
session.save() | |||||
oauth_response = { | |||||
"ok": True, | |||||
"team_name": "foo", | |||||
"incoming_webhook": {"url": "http://example.org", "channel": "bar"}, | |||||
} | |||||
mock_post.return_value.text = json.dumps(oauth_response) | |||||
mock_post.return_value.json.return_value = oauth_response | |||||
url = "/integrations/add_slack_btn/?code=12345678&state=foo" | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(url, follow=True) | |||||
self.assertRedirects(r, "/integrations/") | |||||
self.assertContains(r, "The Slack integration has been added!") | |||||
ch = Channel.objects.get() | |||||
self.assertEqual(ch.slack_team, "foo") | |||||
self.assertEqual(ch.slack_channel, "bar") | |||||
self.assertEqual(ch.slack_webhook_url, "http://example.org") | |||||
self.assertEqual(ch.project, self.project) | |||||
# Session should now be clean | |||||
self.assertFalse("slack" in self.client.session) | |||||
def test_it_avoids_csrf(self): | |||||
session = self.client.session | |||||
session["slack"] = "foo" | |||||
session.save() | |||||
url = "/integrations/add_slack_btn/?code=12345678&state=bar" | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(url) | |||||
self.assertEqual(r.status_code, 400) | |||||
@patch("hc.front.views.requests.post") | |||||
def test_it_handles_oauth_error(self, mock_post): | |||||
session = self.client.session | |||||
session["slack"] = "foo" | |||||
session.save() | |||||
oauth_response = {"ok": False, "error": "something went wrong"} | |||||
mock_post.return_value.text = json.dumps(oauth_response) | |||||
mock_post.return_value.json.return_value = oauth_response | |||||
url = "/integrations/add_slack_btn/?code=12345678&state=foo" | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(url, follow=True) | |||||
self.assertRedirects(r, "/integrations/") | |||||
self.assertContains(r, "something went wrong") | |||||
self.assertTrue("add_slack" in self.client.session) |
@ -0,0 +1,69 @@ | |||||
import json | |||||
from django.test.utils import override_settings | |||||
from hc.api.models import Channel | |||||
from hc.test import BaseTestCase | |||||
from mock import patch | |||||
@override_settings(SLACK_CLIENT_ID="fake-client-id") | |||||
class AddSlackCompleteTestCase(BaseTestCase): | |||||
@patch("hc.front.views.requests.post") | |||||
def test_it_handles_oauth_response(self, mock_post): | |||||
session = self.client.session | |||||
session["add_slack"] = ("foo", str(self.project.code)) | |||||
session.save() | |||||
oauth_response = { | |||||
"ok": True, | |||||
"team_name": "foo", | |||||
"incoming_webhook": {"url": "http://example.org", "channel": "bar"}, | |||||
} | |||||
mock_post.return_value.text = json.dumps(oauth_response) | |||||
mock_post.return_value.json.return_value = oauth_response | |||||
url = "/integrations/add_slack_btn/?code=12345678&state=foo" | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(url, follow=True) | |||||
self.assertRedirects(r, self.channels_url) | |||||
self.assertContains(r, "The Slack integration has been added!") | |||||
ch = Channel.objects.get() | |||||
self.assertEqual(ch.slack_team, "foo") | |||||
self.assertEqual(ch.slack_channel, "bar") | |||||
self.assertEqual(ch.slack_webhook_url, "http://example.org") | |||||
self.assertEqual(ch.project, self.project) | |||||
# Session should now be clean | |||||
self.assertFalse("add_slack" in self.client.session) | |||||
def test_it_avoids_csrf(self): | |||||
session = self.client.session | |||||
session["add_slack"] = ("foo", str(self.project.code)) | |||||
session.save() | |||||
url = "/integrations/add_slack_btn/?code=12345678&state=bar" | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(url) | |||||
self.assertEqual(r.status_code, 403) | |||||
@patch("hc.front.views.requests.post") | |||||
def test_it_handles_oauth_error(self, mock_post): | |||||
session = self.client.session | |||||
session["add_slack"] = ("foo", str(self.project.code)) | |||||
session.save() | |||||
oauth_response = {"ok": False, "error": "something went wrong"} | |||||
mock_post.return_value.text = json.dumps(oauth_response) | |||||
mock_post.return_value.json.return_value = oauth_response | |||||
url = "/integrations/add_slack_btn/?code=12345678&state=foo" | |||||
self.client.login(username="[email protected]", password="password") | |||||
r = self.client.get(url, follow=True) | |||||
self.assertRedirects(r, self.channels_url) | |||||
self.assertContains(r, "something went wrong") |
@ -0,0 +1,9 @@ | |||||
from django.test.utils import override_settings | |||||
from hc.test import BaseTestCase | |||||
@override_settings(SLACK_CLIENT_ID="fake-client-id") | |||||
class AddSlackHelpTestCase(BaseTestCase): | |||||
def test_instructions_work(self): | |||||
r = self.client.get("/integrations/add_slack/") | |||||
self.assertContains(r, "Setup Guide", status_code=200) |
@ -3,97 +3,9 @@ | |||||
{% block title %}Add Slack - {% site_name %}{% endblock %} | {% block title %}Add Slack - {% site_name %}{% endblock %} | ||||
{% block content %} | {% block content %} | ||||
<div class="row"> | <div class="row"> | ||||
<div class="col-sm-12"> | <div class="col-sm-12"> | ||||
{% if slack_client_id %} | |||||
<div class="jumbotron"> | |||||
{% if request.user.is_authenticated %} | |||||
<p>If your team uses <a href="https://slack.com/">Slack</a>, you can set | |||||
up {% site_name %} to post status updates directly to an appropriate | |||||
Slack channel.</p> | |||||
<div class="text-center"> | |||||
<a href="https://slack.com/oauth/authorize?scope=incoming-webhook&client_id={{ slack_client_id }}&state={{ state }}"> | |||||
<img | |||||
alt="Add to Slack" | |||||
src="{% static 'img/integrations/add_to_slack.png' %}" | |||||
srcset="{% static 'img/integrations/add_to_slack.png' %} 1x, {% static 'img/integrations/[email protected]' %} 2x" /> | |||||
</a> | |||||
</div> | |||||
{% else %} | |||||
<p> | |||||
{% site_name %} is a <strong>free</strong> and | |||||
<a href="https://github.com/healthchecks/healthchecks">open source</a> | |||||
service for monitoring your cron jobs, background processes and | |||||
scheduled tasks. Before adding Slack integration, please log into | |||||
{% site_name %}:</p> | |||||
<div class="text-center"> | |||||
<a href="{% url 'hc-login' %}?next={% url 'hc-add-slack' %}" | |||||
class="btn btn-primary btn-lg">Sign In</a> | |||||
</div> | |||||
{% endif %} | |||||
</div> | |||||
<h2>Setup Guide</h2> | |||||
<div class="row ai-step"> | |||||
<div class="col-sm-6"> | |||||
<span class="step-no">1</span> | |||||
<p> | |||||
After {% if request.user.is_authenticated %}{% else %}signing in and{% endif %} | |||||
clicking on "Add to Slack", you should | |||||
be on a page that says "{% site_name %} would like access to | |||||
your Slack team". Select the team you want to add the | |||||
{% site_name %} integration app to. | |||||
</p> | |||||
</div> | |||||
<div class="col-sm-6"> | |||||
<img | |||||
class="ai-guide-screenshot" | |||||
alt="Screenshot" | |||||
src="{% static 'img/integrations/setup_slack_btn_1.png' %}"> | |||||
</div> | |||||
</div> | |||||
<div class="row ai-step"> | |||||
<div class="col-sm-6"> | |||||
<span class="step-no">2</span> | |||||
<p> | |||||
You should now be on a page that says "{% site_name %} would | |||||
like access to <i>TEAM NAME</i>". Select the channel you want to | |||||
post {% site_name %} notifications to. | |||||
</p> | |||||
</div> | |||||
<div class="col-sm-6"> | |||||
<img | |||||
class="ai-guide-screenshot" | |||||
alt="Screenshot" | |||||
src="{% static 'img/integrations/setup_slack_btn_2.png' %}"> | |||||
</div> | |||||
</div> | |||||
<div class="row ai-step"> | |||||
<div class="col-sm-6"> | |||||
<span class="step-no">3</span> | |||||
<p> | |||||
That is all! You will now be redirected back to | |||||
"Integrations" page on {% site_name %} and see | |||||
the new integration! | |||||
</p> | |||||
</div> | |||||
<div class="col-sm-6"> | |||||
<img | |||||
class="ai-guide-screenshot" | |||||
alt="Screenshot" | |||||
src="{% static 'img/integrations/setup_slack_btn_3.png' %}"> | |||||
</div> | |||||
</div> | |||||
{% else %} | |||||
<h1>Slack</h1> | <h1>Slack</h1> | ||||
<p>If your team uses <a href="https://slack.com/">Slack</a>, you can set | <p>If your team uses <a href="https://slack.com/">Slack</a>, you can set | ||||
@ -141,7 +53,7 @@ | |||||
<h2>Integration Settings</h2> | <h2>Integration Settings</h2> | ||||
<form method="post" class="form-horizontal" action="{% url 'hc-add-slack' %}"> | |||||
<form method="post" class="form-horizontal"> | |||||
{% csrf_token %} | {% csrf_token %} | ||||
<div class="form-group {{ form.value.css_classes }}"> | <div class="form-group {{ form.value.css_classes }}"> | ||||
<label for="callback-url" class="col-sm-2 control-label"> | <label for="callback-url" class="col-sm-2 control-label"> | ||||
@ -169,8 +81,6 @@ | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
{% endif %} | |||||
</div> | </div> | ||||
</div> | </div> | ||||
{% endblock %} | {% endblock %} |
@ -0,0 +1,97 @@ | |||||
{% extends "base.html" %} | |||||
{% load humanize static hc_extras %} | |||||
{% block title %}Add Slack - {% site_name %}{% endblock %} | |||||
{% block content %} | |||||
<div class="row"> | |||||
<div class="col-sm-12"> | |||||
<div class="jumbotron"> | |||||
{% if request.user.is_authenticated %} | |||||
<p>If your team uses <a href="https://slack.com/">Slack</a>, you can set | |||||
up {% site_name %} to post status updates directly to an appropriate | |||||
Slack channel.</p> | |||||
<div class="text-center"> | |||||
<a href="{{ authorize_url }}"> | |||||
<img | |||||
alt="Add to Slack" | |||||
src="{% static 'img/integrations/add_to_slack.png' %}" | |||||
srcset="{% static 'img/integrations/add_to_slack.png' %} 1x, {% static 'img/integrations/[email protected]' %} 2x" /> | |||||
</a> | |||||
</div> | |||||
{% else %} | |||||
<p> | |||||
{% site_name %} is a <strong>free</strong> and | |||||
<a href="https://github.com/healthchecks/healthchecks">open source</a> | |||||
service for monitoring your cron jobs, background processes and | |||||
scheduled tasks. Before adding Slack integration, please log into | |||||
{% site_name %}:</p> | |||||
<div class="text-center"> | |||||
<a href="{% url 'hc-login' %}" | |||||
class="btn btn-primary btn-lg">Sign In</a> | |||||
</div> | |||||
{% endif %} | |||||
</div> | |||||
<h2>Setup Guide</h2> | |||||
<div class="row ai-step"> | |||||
<div class="col-sm-6"> | |||||
<span class="step-no">1</span> | |||||
<p> | |||||
After {% if request.user.is_authenticated %}{% else %}signing in and{% endif %} | |||||
clicking on "Add to Slack", you should | |||||
be on a page that says "{% site_name %} would like access to | |||||
your Slack team". Select the team you want to add the | |||||
{% site_name %} integration app to. | |||||
</p> | |||||
</div> | |||||
<div class="col-sm-6"> | |||||
<img | |||||
class="ai-guide-screenshot" | |||||
alt="Screenshot" | |||||
src="{% static 'img/integrations/setup_slack_btn_1.png' %}"> | |||||
</div> | |||||
</div> | |||||
<div class="row ai-step"> | |||||
<div class="col-sm-6"> | |||||
<span class="step-no">2</span> | |||||
<p> | |||||
You should now be on a page that says "{% site_name %} would | |||||
like access to <i>TEAM NAME</i>". Select the channel you want to | |||||
post {% site_name %} notifications to. | |||||
</p> | |||||
</div> | |||||
<div class="col-sm-6"> | |||||
<img | |||||
class="ai-guide-screenshot" | |||||
alt="Screenshot" | |||||
src="{% static 'img/integrations/setup_slack_btn_2.png' %}"> | |||||
</div> | |||||
</div> | |||||
<div class="row ai-step"> | |||||
<div class="col-sm-6"> | |||||
<span class="step-no">3</span> | |||||
<p> | |||||
That is all! You will now be redirected back to | |||||
"Integrations" page on {% site_name %} and see | |||||
the new integration! | |||||
</p> | |||||
</div> | |||||
<div class="col-sm-6"> | |||||
<img | |||||
class="ai-guide-screenshot" | |||||
alt="Screenshot" | |||||
src="{% static 'img/integrations/setup_slack_btn_3.png' %}"> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
{% endblock %} |