From 644363e3bbf5ce88b8a1f749df59b7d74df175e4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 11 Oct 2024 14:23:35 +0800 Subject: [PATCH 01/13] Add tests for getAllMonitors and getMonitorStatsById --- Server/controllers/monitorController.js | 4 +- .../controllers/monitorController.test.js | 113 ++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 Server/tests/controllers/monitorController.test.js diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index b7e27451b..638c1fc02 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -33,7 +33,7 @@ const { handleError, handleValidationError } = require("./controllerUtils"); const getAllMonitors = async (req, res, next) => { try { const monitors = await req.db.getAllMonitors(); - return res.json({ + return res.status(200).json({ success: true, msg: successMessages.MONITOR_GET_ALL, data: monitors, @@ -63,7 +63,7 @@ const getMonitorStatsById = async (req, res, next) => { try { const monitorStats = await req.db.getMonitorStatsById(req); - return res.json({ + return res.status(200).json({ success: true, msg: successMessages.MONITOR_STATS_BY_ID, data: monitorStats, diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js new file mode 100644 index 000000000..7fc950176 --- /dev/null +++ b/Server/tests/controllers/monitorController.test.js @@ -0,0 +1,113 @@ +const { + getAllMonitors, + getMonitorStatsById, + getMonitorCertificate, + getMonitorById, + getMonitorsAndSummaryByTeamId, + getMonitorsByTeamId, + createMonitor, + deleteMonitor, + deleteAllMonitors, + editMonitor, + pauseMonitor, + addDemoMonitors, +} = require("../../controllers/monitorController"); +const jwt = require("jsonwebtoken"); +const { errorMessages, successMessages } = require("../../utils/messages"); +const sinon = require("sinon"); + +describe("Monitor Controller - getAllMonitors", () => { + beforeEach(() => { + req = { + params: {}, + query: {}, + body: {}, + db: { + getAllMonitors: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if DB operations fail", async () => { + req.db.getAllMonitors.throws(new Error("DB error")); + await getAllMonitors(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + + it("should return success message and data if all operations succeed", async () => { + const data = [{ monitor: "data" }]; + req.db.getAllMonitors.returns(data); + await getAllMonitors(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_GET_ALL, + data: data, + }) + ).to.be.true; + }); +}); + +describe("Monitor Controller - getMonitorStatsById", () => { + beforeEach(() => { + req = { + params: { + monitorId: "123", + }, + query: {}, + body: {}, + db: { + getMonitorStatsById: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + + it("should reject with an error if param validation fails", async () => { + req.params = {}; + await getMonitorStatsById(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if query validation fails", async () => { + req.query = { invalid: 1 }; + await getMonitorStatsById(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if DB operations fail", async () => { + req.db.getMonitorStatsById.throws(new Error("DB error")); + await getMonitorStatsById(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should return success message and data if all operations succeed", async () => { + const data = [{ monitorStats: "data" }]; + req.db.getMonitorStatsById.returns(data); + await getMonitorStatsById(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_STATS_BY_ID, + data: data, + }) + ).to.be.true; + }); +}); From 3e23d979300ff368a036112e63077085669b8989 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 11 Oct 2024 14:59:55 +0800 Subject: [PATCH 02/13] Add tests for getMonitorCertificate --- Server/controllers/monitorController.js | 4 +- Server/package-lock.json | 98 +++++++++++++++++++ Server/package.json | 1 + .../controllers/monitorController.test.js | 98 ++++++++++++++++++- 4 files changed, 198 insertions(+), 3 deletions(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 638c1fc02..3aeb21fa0 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -86,7 +86,7 @@ const getMonitorCertificate = async (req, res, next) => { const monitorUrl = new URL(monitor.url); const certificate = await sslChecker(monitorUrl.hostname); if (certificate && certificate.validTo) { - return res.json({ + return res.status(200).json({ success: true, msg: successMessages.MONITOR_CERTIFICATE, data: { @@ -94,7 +94,7 @@ const getMonitorCertificate = async (req, res, next) => { }, }); } else { - return res.json({ + return res.status(200).json({ success: true, msg: successMessages.MONITOR_CERTIFICATE, data: { certificateDate: "N/A" }, diff --git a/Server/package-lock.json b/Server/package-lock.json index bdd7db873..09793bb98 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -36,6 +36,7 @@ "devDependencies": { "nodemon": "3.1.0", "nyc": "17.1.0", + "proxyquire": "2.1.3", "sinon": "19.0.2" } }, @@ -2802,6 +2803,20 @@ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3535,6 +3550,22 @@ "node": ">=8" } }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3575,6 +3606,16 @@ "node": ">=0.12.0" } }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -4968,6 +5009,13 @@ "node": ">=10" } }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true, + "license": "MIT" + }, "node_modules/mongodb": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.2.tgz", @@ -5833,6 +5881,13 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -6545,6 +6600,18 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -6683,6 +6750,24 @@ "dev": true, "license": "ISC" }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -7242,6 +7327,19 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/svgo": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", diff --git a/Server/package.json b/Server/package.json index 0ada3d893..cb6997132 100644 --- a/Server/package.json +++ b/Server/package.json @@ -38,6 +38,7 @@ "devDependencies": { "nodemon": "3.1.0", "nyc": "17.1.0", + "proxyquire": "2.1.3", "sinon": "19.0.2" } } diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 7fc950176..242a52523 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -13,8 +13,14 @@ const { addDemoMonitors, } = require("../../controllers/monitorController"); const jwt = require("jsonwebtoken"); -const { errorMessages, successMessages } = require("../../utils/messages"); +const proxyquire = require("proxyquire"); const sinon = require("sinon"); +const { errorMessages, successMessages } = require("../../utils/messages"); +const { valid } = require("joi"); +const sslCheckerStub = sinon.stub(); +const monitorController = proxyquire("../../controllers/monitorController", { + "ssl-checker": sslCheckerStub, +}); describe("Monitor Controller - getAllMonitors", () => { beforeEach(() => { @@ -111,3 +117,93 @@ describe("Monitor Controller - getMonitorStatsById", () => { ).to.be.true; }); }); + +describe("Monitor Controller - getMonitorCertificate", () => { + beforeEach(() => { + req = { + params: { + monitorId: "123", + }, + query: {}, + body: {}, + db: { + getMonitorById: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + + it("should reject with an error if param validation fails", async () => { + req.params = {}; + await getMonitorCertificate(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if creating a URL fails", async () => { + req.db.getMonitorById.returns(null); + await getMonitorCertificate(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0]).to.be.instanceOf(TypeError); + }); + it("should reject with an error if SSL checker fails", async () => { + req.db.getMonitorById.returns({ url: "https://example.com" }); + sslCheckerStub.throws(new Error("SSL checker error")); + await monitorController.getMonitorCertificate(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("SSL checker error"); + }); + it("should return success message and data if all operations succeed", async () => { + req.db.getMonitorById.returns({ url: "https://example.com" }); + const certificate = { validTo: 1 }; + sslCheckerStub.returns(certificate); + await monitorController.getMonitorCertificate(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_CERTIFICATE, + data: { + certificateDate: new Date(certificate.validTo).toLocaleDateString(), + }, + }) + ).to.be.true; + }); + it("should return success message and N/A if certificate is not found", async () => { + req.db.getMonitorById.returns({ url: "https://example.com" }); + sslCheckerStub.returns(null); + await monitorController.getMonitorCertificate(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_CERTIFICATE, + data: { + certificateDate: "N/A", + }, + }) + ).to.be.true; + }); + + it("should return success message and N/A if certificate doesn't have validTo", async () => { + req.db.getMonitorById.returns({ url: "https://example.com" }); + sslCheckerStub.returns({ validFrom: 1 }); + await monitorController.getMonitorCertificate(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_CERTIFICATE, + data: { + certificateDate: "N/A", + }, + }) + ).to.be.true; + }); +}); From 586b18d295ed351d4a882f191d548392cb909449 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 11 Oct 2024 15:07:37 +0800 Subject: [PATCH 03/13] Add tests for getMonitorById --- Server/controllers/monitorController.js | 3 +- .../controllers/monitorController.test.js | 63 ++++++++++++++++++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 3aeb21fa0..52a60307d 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -130,9 +130,10 @@ const getMonitorById = async (req, res, next) => { if (!monitor) { const error = new Error(errorMessages.MONITOR_GET_BY_ID); error.status = 404; + throw error; } - return res.json({ + return res.status(200).json({ success: true, msg: successMessages.MONITOR_GET_BY_ID, data: monitor, diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 242a52523..7e51a73ed 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -16,7 +16,6 @@ const jwt = require("jsonwebtoken"); const proxyquire = require("proxyquire"); const sinon = require("sinon"); const { errorMessages, successMessages } = require("../../utils/messages"); -const { valid } = require("joi"); const sslCheckerStub = sinon.stub(); const monitorController = proxyquire("../../controllers/monitorController", { "ssl-checker": sslCheckerStub, @@ -193,7 +192,7 @@ describe("Monitor Controller - getMonitorCertificate", () => { it("should return success message and N/A if certificate doesn't have validTo", async () => { req.db.getMonitorById.returns({ url: "https://example.com" }); - sslCheckerStub.returns({ validFrom: 1 }); + sslCheckerStub.returns({ valid: 1 }); await monitorController.getMonitorCertificate(req, res, next); expect(res.status.firstCall.args[0]).to.equal(200); expect( @@ -207,3 +206,63 @@ describe("Monitor Controller - getMonitorCertificate", () => { ).to.be.true; }); }); + +describe("Monitor Controller - getMonitorById", () => { + beforeEach(() => { + req = { + params: { + monitorId: "123", + }, + query: {}, + body: {}, + db: { + getMonitorById: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if param validation fails", async () => { + req.params = {}; + await getMonitorById(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if query param validation fails", async () => { + req.query = { invalid: 1 }; + await getMonitorById(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if DB operations fail", async () => { + req.db.getMonitorById.throws(new Error("DB error")); + await getMonitorById(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should return 404 if a monitor is not found", async () => { + req.db.getMonitorById.returns(null); + await getMonitorById(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(404); + }); + it("should return success message and data if all operations succeed", async () => { + const data = { monitor: "data" }; + req.db.getMonitorById.returns(data); + await getMonitorById(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_GET_BY_ID, + data: data, + }) + ).to.be.true; + }); +}); From 73e087982bbfc0613a3ffac2d4b98f9ccc473171 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 11 Oct 2024 15:10:54 +0800 Subject: [PATCH 04/13] Add tests for getMonitorsAndSummaryByTeamId --- .../controllers/monitorController.test.js | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 7e51a73ed..971ee3c63 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -266,3 +266,58 @@ describe("Monitor Controller - getMonitorById", () => { ).to.be.true; }); }); + +describe("Monitor Controller - getMonitorsAndSummaryByTeamId", () => { + beforeEach(() => { + req = { + params: { + teamId: "123", + }, + query: {}, + body: {}, + db: { + getMonitorsAndSummaryByTeamId: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + + it("should reject with an error if param validation fails", async () => { + req.params = {}; + await getMonitorsAndSummaryByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if query validation fails", async () => { + req.query = { invalid: 1 }; + await getMonitorsAndSummaryByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if DB operations fail", async () => { + req.db.getMonitorsAndSummaryByTeamId.throws(new Error("DB error")); + await getMonitorsAndSummaryByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should return success message and data if all operations succeed", async () => { + const data = { monitors: "data", summary: "data" }; + req.db.getMonitorsAndSummaryByTeamId.returns(data); + await getMonitorsAndSummaryByTeamId(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId), + data: data, + }) + ).to.be.true; + }); +}); From a0b651fd24119e4c8c2e797670acb2bd0e845171 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Oct 2024 09:43:47 +0800 Subject: [PATCH 05/13] Added getMoniotrsByTeamId tests --- Server/controllers/monitorController.js | 2 +- .../controllers/monitorController.test.js | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 52a60307d..06800d5fc 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -208,7 +208,7 @@ const getMonitorsByTeamId = async (req, res, next) => { try { const teamId = req.params.teamId; const monitors = await req.db.getMonitorsByTeamId(req, res); - return res.json({ + return res.status(200).json({ success: true, msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), data: monitors, diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 971ee3c63..636c2beb9 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -321,3 +321,57 @@ describe("Monitor Controller - getMonitorsAndSummaryByTeamId", () => { ).to.be.true; }); }); + +describe("Monitor Controller - getMonitorsByTeamId", () => { + beforeEach(() => { + req = { + params: { + teamId: "123", + }, + query: {}, + body: {}, + db: { + getMonitorsByTeamId: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if param validation fails", async () => { + req.params = {}; + await getMonitorsByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if query validation fails", async () => { + req.query = { invalid: 1 }; + await getMonitorsByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if DB operations fail", async () => { + req.db.getMonitorsByTeamId.throws(new Error("DB error")); + await getMonitorsByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should return success message and data if all operations succeed", async () => { + const data = { monitors: "data" }; + req.db.getMonitorsByTeamId.returns(data); + await getMonitorsByTeamId(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId), + data: data, + }) + ).to.be.true; + }); +}); From 5d1ad0d92f51552ddc9c2c58f350f90f800554a4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Oct 2024 09:53:08 +0800 Subject: [PATCH 06/13] Add tests for createMonitor --- .../controllers/monitorController.test.js | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 636c2beb9..3b6f57275 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -375,3 +375,64 @@ describe("Monitor Controller - getMonitorsByTeamId", () => { ).to.be.true; }); }); + +describe("Monitor Controller - createMonitor", () => { + beforeEach(() => { + req = { + params: {}, + query: {}, + body: { + userId: "123", + teamId: "123", + name: "test_monitor", + description: "test_monitor_desc", + type: "http", + url: "https://example.com", + notifications: [{ email: "example@example.com" }], + }, + db: { + createMonitor: sinon.stub(), + createNotification: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + monitor = { + save: sinon.stub(), + }; + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if body validation fails", async () => { + req.body = {}; + await createMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if DB createMonitor operation fail", async () => { + req.db.createMonitor.throws(new Error("DB error")); + await createMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should reject with an error if DB createNotification operation fail", async () => { + req.db.createNotification.throws(new Error("DB error")); + req.db.createMonitor.returns({ _id: "123" }); + await createMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should reject with an error if DB createNotification operation fail", async () => { + req.db.createMonitor.returns({ + _id: "123", + save: sinon.stub().throws(new Error("DB error")), + }); + await createMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); +}); From 9daa6807019da23dd9bac0ad5303c34589c472e7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Oct 2024 10:13:54 +0800 Subject: [PATCH 07/13] Add tests for deleteMonitor --- .../controllers/monitorController.test.js | 123 +++++++++++++++++- 1 file changed, 120 insertions(+), 3 deletions(-) diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 3b6f57275..2a2e6d040 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -20,6 +20,8 @@ const sslCheckerStub = sinon.stub(); const monitorController = proxyquire("../../controllers/monitorController", { "ssl-checker": sslCheckerStub, }); +const logger = require("../../utils/logger"); +const SERVICE_NAME = "monitorController"; describe("Monitor Controller - getAllMonitors", () => { beforeEach(() => { @@ -400,9 +402,6 @@ describe("Monitor Controller - createMonitor", () => { json: sinon.stub(), }; next = sinon.stub(); - monitor = { - save: sinon.stub(), - }; }); afterEach(() => { sinon.restore(); @@ -436,3 +435,121 @@ describe("Monitor Controller - createMonitor", () => { expect(next.firstCall.args[0].message).to.equal("DB error"); }); }); + +describe("Monitor Controller - deleteMonitor", () => { + beforeEach(() => { + req = { + params: { + monitorId: "123", + }, + query: {}, + body: {}, + db: { + deleteMonitor: sinon.stub(), + deleteChecks: sinon.stub(), + deletePageSpeedChecksByMonitorId: sinon.stub(), + deleteNotificationsByMonitorId: sinon.stub(), + }, + jobQueue: { + deleteJob: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + sinon.stub(logger, "error"); + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if param validation fails", async () => { + req.params = {}; + await deleteMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if DB deleteMonitor operation fail", async () => { + req.db.deleteMonitor.throws(new Error("DB error")); + await deleteMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should log an error if deleteJob throws an error", async () => { + const error = new Error("Job error"); + const monitor = { name: "test_monitor", _id: "123" }; + req.db.deleteMonitor.returns(monitor); + req.jobQueue.deleteJob.rejects(error); + await monitorController.deleteMonitor(req, res, next); + expect(logger.error.calledOnce).to.be.true; + expect(logger.error.firstCall.args[0]).to.equal( + `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}` + ); + expect(logger.error.firstCall.args[1]).to.deep.equal({ + method: "deleteMonitor", + service: SERVICE_NAME, + error: error.message, + }); + }); + it("should log an error if deleteChecks throws an error", async () => { + const error = new Error("Checks error"); + const monitor = { name: "test_monitor", _id: "123" }; + req.db.deleteMonitor.returns(monitor); + req.db.deleteChecks.rejects(error); + await monitorController.deleteMonitor(req, res, next); + expect(logger.error.calledOnce).to.be.true; + expect(logger.error.firstCall.args[0]).to.equal( + `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}` + ); + expect(logger.error.firstCall.args[1]).to.deep.equal({ + method: "deleteMonitor", + service: SERVICE_NAME, + error: error.message, + }); + }); + it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async () => { + const error = new Error("PageSpeed error"); + const monitor = { name: "test_monitor", _id: "123" }; + req.db.deleteMonitor.returns(monitor); + req.db.deletePageSpeedChecksByMonitorId.rejects(error); + await monitorController.deleteMonitor(req, res, next); + expect(logger.error.calledOnce).to.be.true; + expect(logger.error.firstCall.args[0]).to.equal( + `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}` + ); + expect(logger.error.firstCall.args[1]).to.deep.equal({ + method: "deleteMonitor", + service: SERVICE_NAME, + error: error.message, + }); + }); + it("should log an error if deleteNotificationsByMonitorId throws an error", async () => { + const error = new Error("Notifications error"); + const monitor = { name: "test_monitor", _id: "123" }; + req.db.deleteMonitor.returns(monitor); + req.db.deleteNotificationsByMonitorId.rejects(error); + await monitorController.deleteMonitor(req, res, next); + expect(logger.error.calledOnce).to.be.true; + expect(logger.error.firstCall.args[0]).to.equal( + `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}` + ); + expect(logger.error.firstCall.args[1]).to.deep.equal({ + method: "deleteMonitor", + service: SERVICE_NAME, + error: error.message, + }); + }); + it("should return success message if all operations succeed", async () => { + const monitor = { name: "test_monitor", _id: "123" }; + req.db.deleteMonitor.returns(monitor); + await monitorController.deleteMonitor(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_DELETE, + }) + ).to.be.true; + }); +}); From bdde060f7f8a1c7f62f25c5cabd68a17080ab5f7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Oct 2024 11:06:45 +0800 Subject: [PATCH 08/13] Add tests for deleteAllMonitors --- Server/controllers/controllerUtils.js | 4 +- Server/controllers/monitorController.js | 25 ++-- .../controllers/monitorController.test.js | 108 ++++++++++++++++++ 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/Server/controllers/controllerUtils.js b/Server/controllers/controllerUtils.js index 1b3994f2f..3238cd2a1 100644 --- a/Server/controllers/controllerUtils.js +++ b/Server/controllers/controllerUtils.js @@ -6,8 +6,8 @@ const handleValidationError = (error, serviceName) => { return error; }; -const handleError = (error, serviceName, method, code = 500) => { - error.code === undefined ? (error.code = code) : null; +const handleError = (error, serviceName, method, status = 500) => { + error.status === undefined ? (error.status = status) : null; error.service === undefined ? (error.service = serviceName) : null; error.method === undefined ? (error.method = method) : null; return error; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 06800d5fc..99d86a947 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -325,14 +325,23 @@ const deleteAllMonitors = async (req, res, next) => { const { jwtSecret } = req.settingsService.getSettings(); const { teamId } = jwt.verify(token, jwtSecret); const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId); - await monitors.forEach(async (monitor) => { - await req.jobQueue.deleteJob(monitor); - await req.db.deleteChecks(monitor._id); - await req.db.deleteAlertByMonitorId(monitor._id); - await req.db.deletePageSpeedChecksByMonitorId(monitor._id); - await req.db.deleteNotificationsByMonitorId(monitor._id); - }); - + await Promise.all( + monitors.map(async (monitor) => { + try { + await req.jobQueue.deleteJob(monitor); + await req.db.deleteChecks(monitor._id); + await req.db.deletePageSpeedChecksByMonitorId(monitor._id); + await req.db.deleteNotificationsByMonitorId(monitor._id); + } catch (error) { + logger.error( + `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, + { + method: "deleteAllMonitors", + } + ); + } + }) + ); return res .status(200) .json({ success: true, msg: `Deleted ${deletedCount} monitors` }); diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 2a2e6d040..d2c77bc9d 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -553,3 +553,111 @@ describe("Monitor Controller - deleteMonitor", () => { ).to.be.true; }); }); + +describe("Monitor Controller - deleteAllMonitors", () => { + beforeEach(() => { + stub = sinon.stub(jwt, "verify").callsFake(() => { + return { teamId: "123" }; + }); + req = { + headers: { + authorization: "Bearer token", + }, + params: { + monitorId: "123", + }, + query: {}, + body: {}, + db: { + deleteAllMonitors: sinon.stub(), + deleteChecks: sinon.stub(), + deletePageSpeedChecksByMonitorId: sinon.stub(), + deleteNotificationsByMonitorId: sinon.stub(), + }, + jobQueue: { + deleteJob: sinon.stub(), + }, + settingsService: { + getSettings: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + sinon.stub(logger, "error"); + }); + afterEach(() => { + sinon.restore(); + stub.restore(); + }); + it("should reject with an error if getTokenFromHeaders throws an error", async () => { + req.headers = {}; + await monitorController.deleteAllMonitors(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("No auth headers"); + expect(next.firstCall.args[0].status).to.equal(500); + }); + it("should reject with an error if token validation fails", async () => { + stub.restore(); + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + await monitorController.deleteAllMonitors(req, res, next); + expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); + }); + it("should reject with an error if DB deleteAllMonitors operation fail", async () => { + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + req.db.deleteAllMonitors.throws(new Error("DB error")); + await monitorController.deleteAllMonitors(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should log an error if deleteJob throws an error", async () => { + const monitors = [{ name: "test_monitor", _id: "123" }]; + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); + const error = new Error("Job error"); + req.jobQueue.deleteJob.rejects(error); + await monitorController.deleteAllMonitors(req, res, next); + expect(logger.error.calledOnce).to.be.true; + expect(logger.error.firstCall.args[0]).to.deep.equal( + `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` + ); + }); + it("should log an error if deleteChecks throws an error", async () => { + const monitors = [{ name: "test_monitor", _id: "123" }]; + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); + const error = new Error("Check error"); + req.db.deleteChecks.rejects(error); + await monitorController.deleteAllMonitors(req, res, next); + expect(logger.error.calledOnce).to.be.true; + expect(logger.error.firstCall.args[0]).to.deep.equal( + `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` + ); + }); + it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async () => { + const monitors = [{ name: "test_monitor", _id: "123" }]; + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); + const error = new Error("Pagespeed Check error"); + req.db.deletePageSpeedChecksByMonitorId.rejects(error); + await monitorController.deleteAllMonitors(req, res, next); + expect(logger.error.calledOnce).to.be.true; + expect(logger.error.firstCall.args[0]).to.deep.equal( + `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` + ); + }); + it("should log an error if deleteNotificationsByMonitorId throws an error", async () => { + const monitors = [{ name: "test_monitor", _id: "123" }]; + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); + const error = new Error("Notifications Check error"); + req.db.deleteNotificationsByMonitorId.rejects(error); + await monitorController.deleteAllMonitors(req, res, next); + expect(logger.error.calledOnce).to.be.true; + expect(logger.error.firstCall.args[0]).to.deep.equal( + `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` + ); + }); +}); From 2cf4c93cb06fedf97624ac0c254644ad011514f0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Oct 2024 11:18:23 +0800 Subject: [PATCH 09/13] Add tests for editMonitor --- .../controllers/monitorController.test.js | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index d2c77bc9d..eab6ae9ea 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -661,3 +661,110 @@ describe("Monitor Controller - deleteAllMonitors", () => { ); }); }); + +describe("Monitor Controller - editMonitor", () => { + beforeEach(() => { + req = { + headers: {}, + params: { + monitorId: "123", + }, + query: {}, + body: { + notifications: [{ email: "example@example.com" }], + }, + db: { + getMonitorById: sinon.stub(), + editMonitor: sinon.stub(), + deleteNotificationsByMonitorId: sinon.stub(), + createNotification: sinon.stub(), + }, + jobQueue: { + deleteJob: sinon.stub(), + addJob: sinon.stub(), + }, + settingsService: { + getSettings: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if param validation fails", async () => { + req.params = {}; + await editMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if body validation fails", async () => { + req.body = { invalid: 1 }; + await editMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if getMonitorById operation fails", async () => { + req.db.getMonitorById.throws(new Error("DB error")); + await editMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should reject with an error if editMonitor operation fails", async () => { + req.db.getMonitorById.returns({ teamId: "123" }); + req.db.editMonitor.throws(new Error("DB error")); + await editMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should reject with an error if deleteNotificationsByMonitorId operation fails", async () => { + req.db.getMonitorById.returns({ teamId: "123" }); + req.db.editMonitor.returns({ _id: "123" }); + req.db.deleteNotificationsByMonitorId.throws(new Error("DB error")); + await editMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should reject with an error if createNotification operation fails", async () => { + req.db.getMonitorById.returns({ teamId: "123" }); + req.db.editMonitor.returns({ _id: "123" }); + req.db.createNotification.throws(new Error("DB error")); + await editMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should reject with an error if deleteJob operation fails", async () => { + req.db.getMonitorById.returns({ teamId: "123" }); + req.db.editMonitor.returns({ _id: "123" }); + req.jobQueue.deleteJob.throws(new Error("Job error")); + await editMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("Job error"); + }); + it("should reject with an error if addJob operation fails", async () => { + req.db.getMonitorById.returns({ teamId: "123" }); + req.db.editMonitor.returns({ _id: "123" }); + req.jobQueue.addJob.throws(new Error("Add Job error")); + await editMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("Add Job error"); + }); + it("should return success message with data if all operations succeed", async () => { + const monitor = { _id: "123" }; + req.db.getMonitorById.returns({ teamId: "123" }); + req.db.editMonitor.returns(monitor); + await editMonitor(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_EDIT, + data: monitor, + }) + ).to.be.true; + }); +}); From 516212b1b0af9d785ccf2b9e49072f4fa1563fdd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Oct 2024 11:29:04 +0800 Subject: [PATCH 10/13] Add tests for pauseMonitor method --- .../controllers/monitorController.test.js | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index eab6ae9ea..86bfb4e3c 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -768,3 +768,109 @@ describe("Monitor Controller - editMonitor", () => { ).to.be.true; }); }); + +describe("Monitor Controller - pauseMonitor", () => { + beforeEach(() => { + req = { + headers: {}, + params: { + monitorId: "123", + }, + query: {}, + body: {}, + db: { + getMonitorById: sinon.stub(), + }, + jobQueue: { + deleteJob: sinon.stub(), + addJob: sinon.stub(), + }, + settingsService: { + getSettings: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + + it("should reject with an error if param validation fails", async () => { + req.params = {}; + await pauseMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if getMonitorById operation fails", async () => { + req.db.getMonitorById.throws(new Error("DB error")); + await pauseMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + "it should reject with an error if deleteJob operation fails", + async () => { + const monitor = { _id: req.params.monitorId, active: true }; + req.db.getMonitorById.returns(monitor); + req.jobQueue.deleteJob.throws(new Error("Delete Job error")); + await pauseMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("Delete Job error"); + }; + it("should reject with an error if addJob operation fails", async () => { + const monitor = { _id: req.params.monitorId, active: false }; + req.db.getMonitorById.returns(monitor); + req.jobQueue.addJob.throws(new Error("Add Job error")); + await pauseMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("Add Job error"); + }); + it("should reject with an error if monitor.save operation fails", async () => { + const monitor = { + _id: req.params.monitorId, + active: false, + save: sinon.stub().throws(new Error("Save error")), + }; + req.db.getMonitorById.returns(monitor); + await pauseMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("Save error"); + }); + it("should return success pause message with data if all operations succeed with inactive monitor", async () => { + const monitor = { + _id: req.params.monitorId, + active: false, + save: sinon.stub().resolves(), + }; + req.db.getMonitorById.returns(monitor); + await pauseMonitor(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_PAUSE, + data: monitor, + }) + ).to.be.true; + }); + it("should return success resume message with data if all operations succeed with active monitor", async () => { + const monitor = { + _id: req.params.monitorId, + active: true, + save: sinon.stub().resolves(), + }; + req.db.getMonitorById.returns(monitor); + await pauseMonitor(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_RESUME, + data: monitor, + }) + ).to.be.true; + }); +}); From 607b16d1cd5bdf3d9d7ef0136f16ca2a666079e9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Oct 2024 11:36:49 +0800 Subject: [PATCH 11/13] Add tests for addDemoMonitors --- Server/controllers/monitorController.js | 2 +- .../controllers/monitorController.test.js | 82 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 99d86a947..9ef90fa4b 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -469,7 +469,7 @@ const addDemoMonitors = async (req, res, next) => { return res.status(200).json({ success: true, - message: successMessages.MONITOR_DEMO_ADDED, + msg: successMessages.MONITOR_DEMO_ADDED, data: demoMonitors.length, }); } catch (error) { diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 86bfb4e3c..7e6b4fd8f 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -874,3 +874,85 @@ describe("Monitor Controller - pauseMonitor", () => { ).to.be.true; }); }); + +describe("Monitor Controller - addDemoMonitors", () => { + beforeEach(() => { + stub = sinon.stub(jwt, "verify").callsFake(() => { + return { _id: "123", teamId: "123" }; + }); + req = { + headers: { + authorization: "Bearer token", + }, + params: {}, + query: {}, + body: {}, + db: { + addDemoMonitors: sinon.stub(), + }, + settingsService: { + getSettings: sinon.stub(), + }, + jobQueue: { + addJob: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + stub.restore(); + }); + it("should reject with an error if getTokenFromHeaders fails", async () => { + req.headers = {}; + await addDemoMonitors(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("No auth headers"); + expect(next.firstCall.args[0].status).to.equal(500); + }); + it("should reject with an error if getting settings fails", async () => { + req.settingsService.getSettings.throws(new Error("Settings error")); + await addDemoMonitors(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("Settings error"); + }); + it("should reject with an error if JWT validation fails", async () => { + stub.restore(); + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + await addDemoMonitors(req, res, next); + expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); + }); + it("should reject with an error if addDemoMonitors operation fails", async () => { + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + req.db.addDemoMonitors.throws(new Error("DB error")); + await addDemoMonitors(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should reject with an error if addJob operation fails", async () => { + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + req.db.addDemoMonitors.returns([{ _id: "123" }]); + req.jobQueue.addJob.throws(new Error("Add Job error")); + await addDemoMonitors(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("Add Job error"); + }); + it("should return success message with data if all operations succeed", async () => { + const monitors = [{ _id: "123" }]; + req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); + req.db.addDemoMonitors.returns(monitors); + await addDemoMonitors(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_DEMO_ADDED, + data: monitors.length, + }) + ).to.be.true; + }); +}); From 27d5bce9e3db7ce1e0db47b9b219b6d4300757ed Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Oct 2024 11:57:39 +0800 Subject: [PATCH 12/13] Add tests for complete coverage, fix active -> isActive --- .../tests/controllers/controllerUtils.test.js | 18 ++-- .../controllers/monitorController.test.js | 82 +++++++++++++++---- 2 files changed, 76 insertions(+), 24 deletions(-) diff --git a/Server/tests/controllers/controllerUtils.test.js b/Server/tests/controllers/controllerUtils.test.js index c18570a5f..71d134bb6 100644 --- a/Server/tests/controllers/controllerUtils.test.js +++ b/Server/tests/controllers/controllerUtils.test.js @@ -45,22 +45,22 @@ describe("controllerUtils - handleValidationError", () => { }); describe("handleError", () => { - it("should set code to the provided code if error.code is undefined", () => { + it("should set stats to the provided status if error.code is undefined", () => { const error = {}; const serviceName = "TestService"; const method = "testMethod"; - const code = 400; - const result = handleError(error, serviceName, method, code); - expect(result.code).to.equal(code); + const status = 400; + const result = handleError(error, serviceName, method, status); + expect(result.status).to.equal(status); }); it("should not overwrite error.code if it is already defined", () => { - const error = { code: 404 }; + const error = { status: 404 }; const serviceName = "TestService"; const method = "testMethod"; - const code = 400; - const result = handleError(error, serviceName, method, code); - expect(result.code).to.equal(404); + const status = 400; + const result = handleError(error, serviceName, method, status); + expect(result.status).to.equal(404); }); it("should set service to the provided serviceName if error.service is undefined", () => { @@ -100,6 +100,6 @@ describe("handleError", () => { const serviceName = "TestService"; const method = "testMethod"; const result = handleError(error, serviceName, method); - expect(result.code).to.equal(500); + expect(result.status).to.equal(500); }); }); diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 7e6b4fd8f..12d97702c 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -396,6 +396,9 @@ describe("Monitor Controller - createMonitor", () => { createMonitor: sinon.stub(), createNotification: sinon.stub(), }, + jobQueue: { + addJob: sinon.stub(), + }, }; res = { status: sinon.stub().returnsThis(), @@ -425,14 +428,48 @@ describe("Monitor Controller - createMonitor", () => { expect(next.firstCall.args[0]).to.be.an("error"); expect(next.firstCall.args[0].message).to.equal("DB error"); }); - it("should reject with an error if DB createNotification operation fail", async () => { + it("should reject with an error if monitor.save operation fail", async () => { req.db.createMonitor.returns({ _id: "123", - save: sinon.stub().throws(new Error("DB error")), + save: sinon.stub().throws(new Error("Monitor save error")), }); await createMonitor(req, res, next); expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); + expect(next.firstCall.args[0].message).to.equal("Monitor save error"); + }); + it("should throw an error if addJob operation fails", async () => { + req.db.createMonitor.returns({ _id: "123", save: sinon.stub() }); + req.jobQueue.addJob.throws(new Error("Job error")); + await createMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("Job error"); + }); + it("should return success message and data if all operations succeed with empty notifications", async () => { + req.body.notifications = []; + const monitor = { _id: "123", save: sinon.stub() }; + req.db.createMonitor.returns(monitor); + await createMonitor(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(201); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_CREATE, + data: monitor, + }) + ).to.be.true; + }); + it("should return success message and data if all operations succeed", async () => { + const monitor = { _id: "123", save: sinon.stub() }; + req.db.createMonitor.returns(monitor); + await createMonitor(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(201); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_CREATE, + data: monitor, + }) + ).to.be.true; }); }); @@ -753,6 +790,21 @@ describe("Monitor Controller - editMonitor", () => { expect(next.firstCall.args[0]).to.be.an("error"); expect(next.firstCall.args[0].message).to.equal("Add Job error"); }); + it("should return success message with data if all operations succeed and empty notifications", async () => { + req.body.notifications = []; + const monitor = { _id: "123" }; + req.db.getMonitorById.returns({ teamId: "123" }); + req.db.editMonitor.returns(monitor); + await editMonitor(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MONITOR_EDIT, + data: monitor, + }) + ).to.be.true; + }); it("should return success message with data if all operations succeed", async () => { const monitor = { _id: "123" }; req.db.getMonitorById.returns({ teamId: "123" }); @@ -811,17 +863,17 @@ describe("Monitor Controller - pauseMonitor", () => { expect(next.firstCall.args[0]).to.be.an("error"); expect(next.firstCall.args[0].message).to.equal("DB error"); }); - "it should reject with an error if deleteJob operation fails", - async () => { - const monitor = { _id: req.params.monitorId, active: true }; - req.db.getMonitorById.returns(monitor); - req.jobQueue.deleteJob.throws(new Error("Delete Job error")); - await pauseMonitor(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("Delete Job error"); - }; + it("should reject with an error if deleteJob operation fails", async () => { + const monitor = { _id: req.params.monitorId, isActive: true }; + req.db.getMonitorById.returns(monitor); + req.jobQueue.deleteJob.throws(new Error("Delete Job error")); + await pauseMonitor(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("Delete Job error"); + }); + it("should reject with an error if addJob operation fails", async () => { - const monitor = { _id: req.params.monitorId, active: false }; + const monitor = { _id: req.params.monitorId, isActive: false }; req.db.getMonitorById.returns(monitor); req.jobQueue.addJob.throws(new Error("Add Job error")); await pauseMonitor(req, res, next); @@ -842,7 +894,7 @@ describe("Monitor Controller - pauseMonitor", () => { it("should return success pause message with data if all operations succeed with inactive monitor", async () => { const monitor = { _id: req.params.monitorId, - active: false, + isActive: false, save: sinon.stub().resolves(), }; req.db.getMonitorById.returns(monitor); @@ -859,7 +911,7 @@ describe("Monitor Controller - pauseMonitor", () => { it("should return success resume message with data if all operations succeed with active monitor", async () => { const monitor = { _id: req.params.monitorId, - active: true, + isActive: true, save: sinon.stub().resolves(), }; req.db.getMonitorById.returns(monitor); From 2ac60441a3cbff31b1fc545797ccf27fa4dec3bc Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 14 Oct 2024 11:59:15 +0800 Subject: [PATCH 13/13] Fix deleteAllMonitors test --- Server/tests/controllers/monitorController.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index 12d97702c..e2855e81b 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -631,7 +631,7 @@ describe("Monitor Controller - deleteAllMonitors", () => { }); it("should reject with an error if getTokenFromHeaders throws an error", async () => { req.headers = {}; - await monitorController.deleteAllMonitors(req, res, next); + await deleteAllMonitors(req, res, next); expect(next.firstCall.args[0]).to.be.an("error"); expect(next.firstCall.args[0].message).to.equal("No auth headers"); expect(next.firstCall.args[0].status).to.equal(500); @@ -639,13 +639,13 @@ describe("Monitor Controller - deleteAllMonitors", () => { it("should reject with an error if token validation fails", async () => { stub.restore(); req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); - await monitorController.deleteAllMonitors(req, res, next); + await deleteAllMonitors(req, res, next); expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); }); it("should reject with an error if DB deleteAllMonitors operation fail", async () => { req.settingsService.getSettings.returns({ jwtSecret: "my_secret" }); req.db.deleteAllMonitors.throws(new Error("DB error")); - await monitorController.deleteAllMonitors(req, res, next); + await deleteAllMonitors(req, res, next); expect(next.firstCall.args[0]).to.be.an("error"); expect(next.firstCall.args[0].message).to.equal("DB error"); }); @@ -655,7 +655,7 @@ describe("Monitor Controller - deleteAllMonitors", () => { req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); const error = new Error("Job error"); req.jobQueue.deleteJob.rejects(error); - await monitorController.deleteAllMonitors(req, res, next); + await deleteAllMonitors(req, res, next); expect(logger.error.calledOnce).to.be.true; expect(logger.error.firstCall.args[0]).to.deep.equal( `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` @@ -667,7 +667,7 @@ describe("Monitor Controller - deleteAllMonitors", () => { req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); const error = new Error("Check error"); req.db.deleteChecks.rejects(error); - await monitorController.deleteAllMonitors(req, res, next); + await deleteAllMonitors(req, res, next); expect(logger.error.calledOnce).to.be.true; expect(logger.error.firstCall.args[0]).to.deep.equal( `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` @@ -679,7 +679,7 @@ describe("Monitor Controller - deleteAllMonitors", () => { req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); const error = new Error("Pagespeed Check error"); req.db.deletePageSpeedChecksByMonitorId.rejects(error); - await monitorController.deleteAllMonitors(req, res, next); + await deleteAllMonitors(req, res, next); expect(logger.error.calledOnce).to.be.true; expect(logger.error.firstCall.args[0]).to.deep.equal( `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}` @@ -691,7 +691,7 @@ describe("Monitor Controller - deleteAllMonitors", () => { req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 }); const error = new Error("Notifications Check error"); req.db.deleteNotificationsByMonitorId.rejects(error); - await monitorController.deleteAllMonitors(req, res, next); + await deleteAllMonitors(req, res, next); expect(logger.error.calledOnce).to.be.true; expect(logger.error.firstCall.args[0]).to.deep.equal( `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`