From ecf14e763333c5cd14cb12f03f10abc2e01afac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 15 Aug 2017 19:43:49 -0700 Subject: [PATCH 1/7] Add a test for unsilence button --- assets/static/__mocks__/ajaxSuccessMock.js | 12 +++++++++ assets/static/unsilence.test.js | 30 ++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 assets/static/__mocks__/ajaxSuccessMock.js diff --git a/assets/static/__mocks__/ajaxSuccessMock.js b/assets/static/__mocks__/ajaxSuccessMock.js new file mode 100644 index 000000000..0155f863d --- /dev/null +++ b/assets/static/__mocks__/ajaxSuccessMock.js @@ -0,0 +1,12 @@ +const mockXHR = require("mock-xhr"); + +function ajaxSuccessMockServer() { + var server = new mockXHR.server(); + server.handle = function (request) { + request.setResponseHeader("Content-Type", "application/json"); + request.receive(200, JSON.stringify({"status":"success"})); + }; + return server; +} + +exports.ajaxSuccessMockServer = ajaxSuccessMockServer; diff --git a/assets/static/unsilence.test.js b/assets/static/unsilence.test.js index 1e94b8d4d..5e511d2b5 100644 --- a/assets/static/unsilence.test.js +++ b/assets/static/unsilence.test.js @@ -1,5 +1,31 @@ -test("silence setupSilenceForm()", () => { - window.jQuery = require("jquery"); +const $ = window.jQuery = require("jquery"); +const templatesMock = require("./__mocks__/templatesMock"); + +test("unsilence init()", () => { + var body = templatesMock.loadTemplates(); + body.push( + "" + ); + document.body.innerHTML = body; + + require("bootstrap/js/tooltip.js"); const unsilence = require("./unsilence"); + + const ajaxMock = require("./__mocks__/ajaxSuccessMock").ajaxSuccessMockServer(); + ajaxMock.start(); + unsilence.init(); + // icon should be trash-o before clicking + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(true); + $("button.silence-delete").click(); + // and switch to green check mark in circle after + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(false); + expect($("button > span.fa").hasClass("fa-check-circle")).toBe(true); + expect($("button > span.fa").hasClass("text-success")).toBe(true); + + ajaxMock.stop(); }); From 77ec8d9a8b2db9f30231ca9010ef592de12d98d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 15 Aug 2017 19:45:00 -0700 Subject: [PATCH 2/7] Use correct icon Tests are failing because we're not cleaning icons properly --- assets/static/unsilence.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/static/unsilence.js b/assets/static/unsilence.js index f11d6d671..6eb7a9212 100644 --- a/assets/static/unsilence.js +++ b/assets/static/unsilence.js @@ -18,7 +18,7 @@ function markInProgress(alertmanagerURI, silenceID) { var elem = unsilenceButtonByID(alertmanagerURI, silenceID); elem.attr("title", "Silence is being deleted from Alertmanager"); elem.tooltip("fixTitle"); - elem.find(".fa").removeClass("fa-times").addClass("fa-refresh fa-spin"); + elem.find(".fa").removeClass("fa-trash-o").addClass("fa-refresh fa-spin"); } function markFailed(alertmanagerURI, silenceID, xhr) { @@ -26,12 +26,12 @@ function markFailed(alertmanagerURI, silenceID, xhr) { var elem = unsilenceButtonByID(alertmanagerURI, silenceID); elem.attr("title", err); elem.tooltip("fixTitle"); - elem.find(".fa").removeClass("fa-times fa-refresh fa-spin").addClass("fa-exclamation-circle text-danger"); + elem.find(".fa").removeClass("fa-trash-o fa-refresh fa-spin").addClass("fa-exclamation-circle text-danger"); // Disable button, wait 5s and reset button to the original state elem.data("disabled", "true"); setTimeout(function() { - elem.find(".fa").removeClass("fa-exclamation-circle text-danger").addClass("fa-times"); + elem.find(".fa").removeClass("fa-exclamation-circle text-danger").addClass("fa-trash-o"); elem.removeData("disabled"); elem.attr("title", "Delete this silence"); elem.tooltip("fixTitle"); @@ -42,7 +42,7 @@ function markSuccess(alertmanagerURI, silenceID) { var elem = unsilenceButtonByID(alertmanagerURI, silenceID); elem.attr("title", "Silence deleted from Alertmanager"); elem.tooltip("fixTitle"); - elem.find(".fa").removeClass("fa-times fa-refresh fa-spin").addClass("fa-check-circle text-success"); + elem.find(".fa").removeClass("fa-trash-o fa-refresh fa-spin").addClass("fa-check-circle text-success"); // disable button so it's no longer clickable elem.data("disabled", "true"); } From 431540c05ee782fd5790ffea91c5874584256ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 15 Aug 2017 19:50:58 -0700 Subject: [PATCH 3/7] Add a test case for error handling when unsilence request fails --- assets/static/__mocks__/ajaxErrorMock.js | 16 +++++++++ assets/static/unsilence.test.js | 46 +++++++++++++++++++----- 2 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 assets/static/__mocks__/ajaxErrorMock.js diff --git a/assets/static/__mocks__/ajaxErrorMock.js b/assets/static/__mocks__/ajaxErrorMock.js new file mode 100644 index 000000000..5632939f2 --- /dev/null +++ b/assets/static/__mocks__/ajaxErrorMock.js @@ -0,0 +1,16 @@ +const mockXHR = require("mock-xhr"); + +function ajaxErrorMockServer() { + var server = new mockXHR.server(); + server.handle = function (request) { + request.setResponseHeader("Content-Type", "application/json"); + request.receive(500, JSON.stringify({ + "status": "error", + "errorType": "server_error", + "error": "end time must not be modified for elapsed silence" + })); + }; + return server; +} + +exports.ajaxErrorMockServer = ajaxErrorMockServer; diff --git a/assets/static/unsilence.test.js b/assets/static/unsilence.test.js index 5e511d2b5..792d27978 100644 --- a/assets/static/unsilence.test.js +++ b/assets/static/unsilence.test.js @@ -1,15 +1,18 @@ const $ = window.jQuery = require("jquery"); const templatesMock = require("./__mocks__/templatesMock"); -test("unsilence init()", () => { +const unsilenceButtonHTML = + ""; + +jest.useFakeTimers(); + +test("unsilence button icons after success", () => { var body = templatesMock.loadTemplates(); - body.push( - "" - ); + body.push(unsilenceButtonHTML); document.body.innerHTML = body; require("bootstrap/js/tooltip.js"); @@ -29,3 +32,30 @@ test("unsilence init()", () => { ajaxMock.stop(); }); + +test("unsilence button icons after failed delete", () => { + var body = templatesMock.loadTemplates(); + body.push(unsilenceButtonHTML); + document.body.innerHTML = body; + + require("bootstrap/js/tooltip.js"); + const unsilence = require("./unsilence"); + + const ajaxMock = require("./__mocks__/ajaxErrorMock").ajaxErrorMockServer(); + ajaxMock.start(); + + unsilence.init(); + // icon should be trash-o before clicking + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(true); + $("button.silence-delete").click(); + // and switch to green check mark in circle after + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(false); + expect($("button > span.fa").hasClass("fa-exclamation-circle")).toBe(true); + expect($("button > span.fa").hasClass("text-danger")).toBe(true); + + // run timers, it should reset the icon back to trash-o + jest.runOnlyPendingTimers(); + expect($("button > span.fa").hasClass("fa-trash-o")).toBe(true); + + ajaxMock.stop(); +}); From 866797cc4209ec3b5143316d37d5bc53bf0c21e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 15 Aug 2017 20:20:35 -0700 Subject: [PATCH 4/7] Add more silence.js tests --- assets/static/__mocks__/ajaxMock.js | 12 ++++ .../static/__snapshots__/silence.test.js.snap | 15 +++++ assets/static/silence.js | 1 + assets/static/silence.test.js | 63 ++++++++++++++++++- 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 assets/static/__mocks__/ajaxMock.js create mode 100644 assets/static/__snapshots__/silence.test.js.snap diff --git a/assets/static/__mocks__/ajaxMock.js b/assets/static/__mocks__/ajaxMock.js new file mode 100644 index 000000000..c561cbae6 --- /dev/null +++ b/assets/static/__mocks__/ajaxMock.js @@ -0,0 +1,12 @@ +const mockXHR = require("mock-xhr"); + +function createServer(status, payload) { + var server = new mockXHR.server(); + server.handle = function (request) { + request.setResponseHeader("Content-Type", "application/json"); + request.receive(status, JSON.stringify(payload)); + }; + return server; +} + +exports.createServer = createServer; diff --git a/assets/static/__snapshots__/silence.test.js.snap b/assets/static/__snapshots__/silence.test.js.snap new file mode 100644 index 000000000..5a486013a --- /dev/null +++ b/assets/static/__snapshots__/silence.test.js.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`failed sendSilencePOST() 1`] = ` +"

+ + request failed +

" +`; + +exports[`successful sendSilencePOST() 1`] = ` +"

+ + abcdef +

" +`; diff --git a/assets/static/silence.js b/assets/static/silence.js index 73ccb8366..d4216758e 100644 --- a/assets/static/silence.js +++ b/assets/static/silence.js @@ -328,3 +328,4 @@ function setupSilenceForm() { } exports.setupSilenceForm = setupSilenceForm; +exports.sendSilencePOST = sendSilencePOST; diff --git a/assets/static/silence.test.js b/assets/static/silence.test.js index 2ce6d91dd..52ca7a0aa 100644 --- a/assets/static/silence.test.js +++ b/assets/static/silence.test.js @@ -1,5 +1,66 @@ +const $ = window.jQuery = require("jquery"); +const templatesMock = require("./__mocks__/templatesMock"); +const ajaxMock = require("./__mocks__/ajaxMock"); + test("silence setupSilenceForm()", () => { - window.jQuery = require("jquery"); + var body = templatesMock.loadTemplates(); + document.body.innerHTML = body; + const silence = require("./silence"); silence.setupSilenceForm(); }); + +test("successful sendSilencePOST()", () => { + var body = templatesMock.loadTemplates(); + body.push( + "" + + "" + ); + document.body.innerHTML = body; + + const templates = require("./templates"); + templates.init(); + + const ajaxServer = ajaxMock.createServer(200, { + "status": "success", + "data": {"silenceId": "abcdef"} + }); + ajaxServer.start(); + + const silence = require("./silence"); + silence.sendSilencePOST("http://localhost", {}); + + let resultElem = $(".silence-post-result").html().trim(); + expect(resultElem).toMatchSnapshot(); + + ajaxServer.stop(); +}); + +test("failed sendSilencePOST()", () => { + var body = templatesMock.loadTemplates(); + body.push( + "" + + "" + ); + document.body.innerHTML = body; + + const templates = require("./templates"); + templates.init(); + + const ajaxServer = ajaxMock.createServer(500, { + "status": "success", + "errorType": "server_error", + "error": "request failed" + }); + ajaxServer.start(); + + const silence = require("./silence"); + silence.sendSilencePOST("http://localhost", {}); + + let resultElem = $(".silence-post-result").html().trim(); + expect(resultElem).toMatchSnapshot(); + + ajaxServer.stop(); +}); From ac8b3391d77ae7bab106156575027e16449cc0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 15 Aug 2017 20:22:57 -0700 Subject: [PATCH 5/7] Re-use ajax mocks instead of creating one per response --- assets/static/__mocks__/ajaxErrorMock.js | 16 ---------------- assets/static/__mocks__/ajaxSuccessMock.js | 12 ------------ assets/static/unsilence.test.js | 17 +++++++++++------ 3 files changed, 11 insertions(+), 34 deletions(-) delete mode 100644 assets/static/__mocks__/ajaxErrorMock.js delete mode 100644 assets/static/__mocks__/ajaxSuccessMock.js diff --git a/assets/static/__mocks__/ajaxErrorMock.js b/assets/static/__mocks__/ajaxErrorMock.js deleted file mode 100644 index 5632939f2..000000000 --- a/assets/static/__mocks__/ajaxErrorMock.js +++ /dev/null @@ -1,16 +0,0 @@ -const mockXHR = require("mock-xhr"); - -function ajaxErrorMockServer() { - var server = new mockXHR.server(); - server.handle = function (request) { - request.setResponseHeader("Content-Type", "application/json"); - request.receive(500, JSON.stringify({ - "status": "error", - "errorType": "server_error", - "error": "end time must not be modified for elapsed silence" - })); - }; - return server; -} - -exports.ajaxErrorMockServer = ajaxErrorMockServer; diff --git a/assets/static/__mocks__/ajaxSuccessMock.js b/assets/static/__mocks__/ajaxSuccessMock.js deleted file mode 100644 index 0155f863d..000000000 --- a/assets/static/__mocks__/ajaxSuccessMock.js +++ /dev/null @@ -1,12 +0,0 @@ -const mockXHR = require("mock-xhr"); - -function ajaxSuccessMockServer() { - var server = new mockXHR.server(); - server.handle = function (request) { - request.setResponseHeader("Content-Type", "application/json"); - request.receive(200, JSON.stringify({"status":"success"})); - }; - return server; -} - -exports.ajaxSuccessMockServer = ajaxSuccessMockServer; diff --git a/assets/static/unsilence.test.js b/assets/static/unsilence.test.js index 792d27978..7d2bf3027 100644 --- a/assets/static/unsilence.test.js +++ b/assets/static/unsilence.test.js @@ -1,5 +1,6 @@ const $ = window.jQuery = require("jquery"); const templatesMock = require("./__mocks__/templatesMock"); +const ajaxMock = require("./__mocks__/ajaxMock"); const unsilenceButtonHTML = " + + + + + + + + + + + + + + + + + + + + + + +
+ + 1 + + +
+
+ + 1 + + +
+
+ + 1 + + +
+
+ +
+ +
+
+
January 2050
SuMoTuWeThFrSa
2627282930311
2345678
9101112131415
16171819202122
23242526272829
303112345
2050
JanFebMarAprMayJunJulAugSepOctNovDec
2045-2056
204520462047204820492050205120522053205420552056
2000-2107
2000 - 20112012 - 20232024 - 20352036 - 20472048 - 20592060 - 20712072 - 20832084 - 20952096 - 2107
01:00
00010203
04050607
08091011
12131415
16171819
20212223
00051015
20253035
40455055
+
+
+
January 2050
SuMoTuWeThFrSa
2627282930311
2345678
9101112131415
16171819202122
23242526272829
303112345
2050
JanFebMarAprMayJunJulAugSepOctNovDec
2045-2056
204520462047204820492050205120522053205420552056
2000-2107
2000 - 20112012 - 20232024 - 20352036 - 20472048 - 20592060 - 20712072 - 20832084 - 20952096 - 2107
02:00
00010203
04050607
08091011
12131415
16171819
20212223
00051015
20253035
40455055
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+
0
+
+ days + +
1
+
+ hours + +
0
+
+ minutes +
+ + + + + + + + + + + +
+
+
+
+ +
+
+ + + + +
+
+ +
+
+ + + + +
+
+ +
+ +
+ +
+ +
+
+ +
+
curl http://localhost/api/v1/silences
+  -X POST --data {
+  \\"matchers\\": [
+    {
+      \\"name\\": \\"foo\\",
+      \\"value\\": \\"bar\\",
+      \\"isRegex\\": false
+    }
+  ],
+  \\"startsAt\\": \\"2050-01-01T01:00:00.000Z\\",
+  \\"endsAt\\": \\"2050-01-01T02:00:00.000Z\\",
+  \\"createdBy\\": \\"\\",
+  \\"comment\\": \\"\\"
+}
+
+ + + + " +`; + +exports[`silence form 3`] = ` +"
+ + + + + + + +
+ + mock + + +

+ + abcdef +

+
+
" +`; + exports[`successful sendSilencePOST() 1`] = ` "

diff --git a/assets/static/silence.test.js b/assets/static/silence.test.js index 52ca7a0aa..e653f6473 100644 --- a/assets/static/silence.test.js +++ b/assets/static/silence.test.js @@ -1,13 +1,120 @@ const $ = window.jQuery = require("jquery"); +const moment = require("moment"); const templatesMock = require("./__mocks__/templatesMock"); const ajaxMock = require("./__mocks__/ajaxMock"); -test("silence setupSilenceForm()", () => { - var body = templatesMock.loadTemplates(); +jest.useFakeTimers(); + +test("silence form", () => { + let body = templatesMock.loadTemplates(); + body.push( + "

" + + "" + + "" + ); document.body.innerHTML = body; + const templates = require("./templates"); + templates.init(); + + const config = require("./config"); + config.init({ + CopySelector: "#copy-settings-with-filter", + SaveSelector: "#save-default-filter", + ResetSelector: "#reset-settings" + }); + const silence = require("./silence"); silence.setupSilenceForm(); + + require("bootstrap/js/tooltip.js"); + require("bootstrap/js/modal.js"); + + // rendering silence form requires AJAX call to pull data + // first check failed request + let ajaxServer = ajaxMock.createServer(500, { + "status": "error", + "errorType": "server_error", + "error": "request failed" + }); + ajaxServer.start(); + // click on the button, modal should show and render via ajax call + $("#silenceButton").click(); + jest.runOnlyPendingTimers(); + let silenceModal = $("#silenceModal").html().trim(); + expect(silenceModal).toMatchSnapshot(); + ajaxServer.stop(); + + // hide the form + $("#silenceModal").modal("hide"); + + // next try successful request + ajaxServer = ajaxMock.createServer(200, { + "groups": [ { + "receiver": "default", + "labels": {"alertname": "fakeAlert"}, + "alerts": [ { + "annotations": {}, + "labels": { + "alertname": "fakeAlert", + "cluster": "prod", + "foo": "bar" + }, + "startsAt": "2017-07-22T01:07:54.32189391Z", + "endsAt": "0001-01-01T00:00:00Z", + "state": "active", + "alertmanager": [ { + "name": "mock", + "uri": "http://localhost", + "state": "active", + "startsAt": "2017-07-22T01:07:54.32189391Z", + "endsAt": "0001-01-01T00:00:00Z", + "source": "localhost/prometheus", + "silences": {} + } ], + "receiver": "default", + "links": {} + } ], + "id": "12345", + "hash": "abcdef", + "stateCount": {"active": 1, "suppressed": 0, "unprocessed": 0} + } ] + }); + ajaxServer.start(); + // click on the button, modal should show and render via ajax call + $("#silenceButton").click(); + jest.runOnlyPendingTimers(); + // default times are relative to current time, use fixed values + let startsAt = moment("2050-01-01T01:00:00.000Z").utc(); + let endsAt = moment("2050-01-01T02:00:00.000Z").utc(); + $("#endsAt").data("DateTimePicker").date(endsAt); + $("#startsAt").data("DateTimePicker").date(startsAt); + // compare html to a snapshot + silenceModal = $("#silenceModal").html().trim(); + expect(silenceModal).toMatchSnapshot(); + ajaxServer.stop(); + + // submit silence + ajaxServer = ajaxMock.createServer(200, { + "status": "success", + "data": {"silenceId": "abcdef"} + }); + ajaxServer.start(); + $("#newSilenceForm").submit(); + ajaxServer.stop(); + silenceModal = $("#silenceModal").html().trim(); + expect(silenceModal).toMatchSnapshot(); }); test("successful sendSilencePOST()", () => { From 3124049c5a04daf08c9943f969212cdb59a2854d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Thu, 17 Aug 2017 19:33:16 -0700 Subject: [PATCH 7/7] Fix wrong status in error Doesn't matter for test result, but should be error since we return 500 --- assets/static/silence.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/static/silence.test.js b/assets/static/silence.test.js index e653f6473..0150f02f8 100644 --- a/assets/static/silence.test.js +++ b/assets/static/silence.test.js @@ -157,7 +157,7 @@ test("failed sendSilencePOST()", () => { templates.init(); const ajaxServer = ajaxMock.createServer(500, { - "status": "success", + "status": "error", "errorType": "server_error", "error": "request failed" });