devtools: Add support for send_all in the integrations dev panel.

Using this feature a reviewer can easily send and view all fixture
messages for any given integration - with only JSON fixtures that is.
This commit is contained in:
Hemanth V. Alluri 2019-05-16 23:59:18 +05:30 committed by Tim Abbott
parent d17fb622c9
commit 8214d65336
5 changed files with 181 additions and 24 deletions

View File

@ -62,6 +62,21 @@ function get_selected_integration_name() {
return $("#integration_name").children("option:selected").val(); return $("#integration_name").children("option:selected").val();
} }
function get_custom_http_headers() {
var custom_headers = $("#custom_http_headers").val();
if (custom_headers !== "") {
// JSON.parse("") would trigger an error, as empty strings do not qualify as JSON.
try {
// Let JavaScript validate the JSON for us.
custom_headers = JSON.stringify(JSON.parse(custom_headers));
} catch (err) {
set_message("Custom HTTP headers are not in a valid JSON format.", "warning");
return;
}
}
return custom_headers;
}
function load_fixture_body(fixture_name) { function load_fixture_body(fixture_name) {
/* Given a fixture name, use the loaded_fixtures dictionary to set the fixture body field. */ /* Given a fixture name, use the loaded_fixtures dictionary to set the fixture body field. */
var integration_name = get_selected_integration_name(); var integration_name = get_selected_integration_name();
@ -191,17 +206,7 @@ function send_webhook_fixture_message() {
return; return;
} }
var custom_headers = $("#custom_http_headers").val(); var custom_headers = get_custom_http_headers();
if (custom_headers !== "") {
// JSON.parse("") would trigger an error, as empty strings do not qualify as JSON.
try {
// Let JavaScript validate the JSON for us.
custom_headers = JSON.stringify(JSON.parse(custom_headers));
} catch (err) {
set_message("Custom HTTP headers are not in a valid JSON format.", "warning");
return;
}
}
channel.post({ channel.post({
url: "/devtools/integrations/check_send_webhook_fixture_message", url: "/devtools/integrations/check_send_webhook_fixture_message",
@ -224,6 +229,38 @@ function send_webhook_fixture_message() {
return; return;
} }
function send_all_fixture_messages() {
/* Send all fixture messages for a given integration. */
var url = $("#URL").val();
var integration = get_selected_integration_name();
if (integration === "") {
set_message("You have to select an integration first.");
return;
}
var custom_headers = get_custom_http_headers();
var csrftoken = $("#csrftoken").val();
channel.post({
url: "/devtools/integrations/send_all_webhook_fixture_messages",
data: {url: url, custom_headers: custom_headers, integration_name: integration},
beforeSend: function (xhr) {xhr.setRequestHeader('X-CSRFToken', csrftoken);},
success: function (response) {
// This is a somewhat sloppy construction, but ultimately, it's a devtool.
var responses = response.responses;
var data = "Results:\n\n";
responses.forEach(function (response) {
data += "Fixture: " + response.fixture_name + "\nStatus Code: ";
data += response.status_code + "\nResponse: " + response.message + "\n\n";
});
$("#fixture_body")[0].value = data;
},
error: handle_unsuccessful_response,
});
return;
}
// Initialization // Initialization
$(function () { $(function () {
clear_elements(["stream_name", "topic_name", "URL", "bot_name", "integration_name", clear_elements(["stream_name", "topic_name", "URL", "bot_name", "integration_name",
@ -257,6 +294,11 @@ $(function () {
return; return;
}); });
$('#send_all_fixtures_button').click(function () {
send_all_fixture_messages();
return;
});
$("#bot_name").change(update_url); $("#bot_name").change(update_url);
$("#stream_name").change(update_url); $("#stream_name").change(update_url);

View File

@ -84,8 +84,14 @@
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="2"> <p id="message"></p> </td>
<p id="message"></p> </tr>
<tr>
<td>
<button id="send_all_fixtures_button" class="btn-success">Send All</button>
</td>
<td>
<button id="send_fixture_button" class="btn-success">Send!</button> <button id="send_fixture_button" class="btn-success">Send!</button>
</td> </td>
</tr> </tr>

View File

@ -103,3 +103,75 @@ class TestIntegrationsDevPanel(ZulipTestCase):
target_url = "/devtools/integrations/" target_url = "/devtools/integrations/"
response = self.client_get(target_url) response = self.client_get(target_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_send_all_webhook_fixture_messages_for_success(self) -> None:
bot = get_user('webhook-bot@zulip.com', self.zulip_realm)
url = "/api/v1/external/appfollow?api_key={key}&stream=Denmark&topic=Appfollow Bulk Notifications".format(key=bot.api_key)
target_url = "/devtools/integrations/send_all_webhook_fixture_messages"
data = {
"url": url,
"custom_headers": "",
"integration_name": "appfollow",
}
response = self.client_post(target_url, data)
expected_responses = [
{
"fixture_name": "sample.json",
"status_code": 200,
"message": {"msg": "", "result": "success"}
},
{
"fixture_name": "review.json",
"status_code": 200,
"message": {"msg": "", "result": "success"}
}
]
responses = ujson.loads(response.content)["responses"]
for r in responses:
r["message"] = ujson.loads(r["message"])
self.assertEqual(response.status_code, 200)
for r in responses:
# We have to use this roundabout manner since the order may vary each time. This is not
# an issue.
self.assertTrue(r in expected_responses)
expected_responses.remove(r)
new_messages = Message.objects.order_by('-id')[0:2]
expected_messages = ["Webhook integration was successful.\nTest User / Acme (Google Play)", "Acme - Group chat\nApp Store, Acme Technologies, Inc.\n★★★★★ United States\n**Great for Information Management**\nAcme enables me to manage the flow of information quite well. I only wish I could create and edit my Acme Post files in the iOS app.\n*by* **Mr RESOLUTIONARY** *for v3.9*\n[Permalink](http://appfollow.io/permalink) · [Add tag](http://watch.appfollow.io/add_tag)"]
for msg in new_messages:
# new_messages -> expected_messages or expected_messages -> new_messages shouldn't make
# a difference since equality is commutative.
self.assertTrue(msg.content in expected_messages)
expected_messages.remove(msg.content)
self.assertEqual(Stream.objects.get(id=msg.recipient.type_id).name, "Denmark")
self.assertEqual(msg.topic_name(), "Appfollow Bulk Notifications")
@patch("zerver.views.development.integrations.os.path.exists")
def test_send_all_webhook_fixture_messages_for_missing_fixtures(self, os_path_exists_mock: MagicMock) -> None:
os_path_exists_mock.return_value = False
bot = get_user('webhook-bot@zulip.com', self.zulip_realm)
url = "/api/v1/external/appfollow?api_key={key}&stream=Denmark&topic=Appfollow Bulk Notifications".format(key=bot.api_key)
data = {
"url": url,
"custom_headers": "",
"integration_name": "appfollow",
}
response = self.client_post("/devtools/integrations/send_all_webhook_fixture_messages", data)
expected_response = {'msg': 'The integration "appfollow" does not have fixtures.', 'result': 'error'}
self.assertEqual(response.status_code, 404)
self.assertEqual(ujson.loads(response.content), expected_response)
def test_send_all_webhook_fixture_messages_for_non_json_fixtures(self) -> None:
bot = get_user('webhook-bot@zulip.com', self.zulip_realm)
url = "/api/v1/external/appfollow?api_key={key}&stream=Denmark&topic=Desktdotcom Bulk Notifications".format(key=bot.api_key)
data = {
"url": url,
"custom_headers": "",
"integration_name": "deskdotcom",
}
response = self.client_post("/devtools/integrations/send_all_webhook_fixture_messages", data)
expected_response = {'msg': 'The integration "deskdotcom" has non-JSON fixtures.', 'result': 'error'}
self.assertEqual(response.status_code, 400)
self.assertEqual(ujson.loads(response.content), expected_response)

View File

@ -27,6 +27,22 @@ def dev_panel(request: HttpRequest) -> HttpResponse:
return render(request, "zerver/integrations/development/dev_panel.html", context) return render(request, "zerver/integrations/development/dev_panel.html", context)
def send_webhook_fixture_message(url: str=REQ(),
body: str=REQ(),
custom_headers: str=REQ()) -> HttpResponse:
client = Client()
realm = get_realm("zulip")
try:
headers = parse_headers(custom_headers)
except ValueError as ve:
return json_error("Custom HTTP headers are not in a valid JSON format. {}".format(ve)) # nolint
if not headers:
headers = {}
http_host = headers.pop("HTTP_HOST", realm.host)
content_type = headers.pop("HTTP_CONTENT_TYPE", "application/json")
return client.post(url, body, content_type=content_type, HTTP_HOST=http_host, **headers)
@has_request_variables @has_request_variables
def get_fixtures(request: HttpResponse, def get_fixtures(request: HttpResponse,
integration_name: str=REQ()) -> HttpResponse: integration_name: str=REQ()) -> HttpResponse:
@ -61,18 +77,37 @@ def check_send_webhook_fixture_message(request: HttpRequest,
url: str=REQ(), url: str=REQ(),
body: str=REQ(), body: str=REQ(),
custom_headers: str=REQ()) -> HttpResponse: custom_headers: str=REQ()) -> HttpResponse:
client = Client() response = send_webhook_fixture_message(url, body, custom_headers)
realm = get_realm("zulip")
try:
headers = parse_headers(custom_headers)
except ValueError as ve:
return json_error("Custom HTTP headers are not in a valid JSON format. {}".format(ve)) # nolint
if not headers:
headers = {}
http_host = headers.pop("HTTP_HOST", realm.host)
content_type = headers.pop("HTTP_CONTENT_TYPE", "application/json")
response = client.post(url, body, content_type=content_type, HTTP_HOST=http_host, **headers)
if response.status_code == 200: if response.status_code == 200:
return json_success() return json_success()
else: else:
return response return response
@has_request_variables
def send_all_webhook_fixture_messages(request: HttpRequest,
url: str=REQ(),
custom_headers: str=REQ(),
integration_name: str=REQ()) -> HttpResponse:
fixtures_dir = os.path.join(ZULIP_PATH, "zerver/webhooks/{integration_name}/fixtures".format(
integration_name=integration_name))
if not os.path.exists(fixtures_dir):
msg = ("The integration \"{integration_name}\" does not have fixtures.").format(
integration_name=integration_name)
return json_error(msg, status=404)
responses = []
for fixture in os.listdir(fixtures_dir):
fixture_path = os.path.join(fixtures_dir, fixture)
try:
# Just a quick check to make sure that the fixtures are of a valid JSON format.
json_data = ujson.dumps(ujson.loads(open(fixture_path).read()))
except ValueError:
msg = ("The integration \"{integration_name}\" has non-JSON fixtures.").format(
integration_name=integration_name)
return json_error(msg)
response = send_webhook_fixture_message(url, json_data, custom_headers)
responses.append({"status_code": response.status_code,
"fixture_name": fixture,
"message": response.content})
return json_success({"responses": responses})

View File

@ -54,6 +54,8 @@ urls = [
url(r'^devtools/integrations/$', zerver.views.development.integrations.dev_panel), url(r'^devtools/integrations/$', zerver.views.development.integrations.dev_panel),
url(r'^devtools/integrations/check_send_webhook_fixture_message$', url(r'^devtools/integrations/check_send_webhook_fixture_message$',
zerver.views.development.integrations.check_send_webhook_fixture_message), zerver.views.development.integrations.check_send_webhook_fixture_message),
url(r'^devtools/integrations/send_all_webhook_fixture_messages$',
zerver.views.development.integrations.send_all_webhook_fixture_messages),
url(r'^devtools/integrations/(?P<integration_name>.+)/fixtures$', url(r'^devtools/integrations/(?P<integration_name>.+)/fixtures$',
zerver.views.development.integrations.get_fixtures), zerver.views.development.integrations.get_fixtures),
] ]