From 3721af755503f5a485e7a52c5acda22988d395f6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:52:01 +0800 Subject: [PATCH 01/12] Add hardware incident email template --- Server/templates/hardwareIncident.mjml | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Server/templates/hardwareIncident.mjml diff --git a/Server/templates/hardwareIncident.mjml b/Server/templates/hardwareIncident.mjml new file mode 100644 index 000000000..e27933fb9 --- /dev/null +++ b/Server/templates/hardwareIncident.mjml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + Message from BlueWave Infrastructure Monitoring + + + + + Infrastructure Alerts + + + + + + + +

Hello {{name}}!

+

{{monitor}} at {{url}} has the following infrastructure alerts:

+ {{#each alerts}} +

• {{this}}

+ {{/each}} +
+
+ + + View Infrastructure Details + +

This email was sent by BlueWave Infrastructure Monitoring.

+
+
+
+
+
\ No newline at end of file From 930403af8cd9ae2dc9631a08fa13d55f97fccfe2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:52:32 +0800 Subject: [PATCH 02/12] Update Notification model to include metric specific notification thresholds --- Server/db/models/Notification.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Server/db/models/Notification.js b/Server/db/models/Notification.js index 9edb0187a..868324cc2 100644 --- a/Server/db/models/Notification.js +++ b/Server/db/models/Notification.js @@ -16,6 +16,28 @@ const NotificationSchema = mongoose.Schema( phone: { type: String, }, + alertThreshold: { + type: Number, + default: 5, + }, + cpuAlertThreshold: { + type: Number, + default: function () { + return this.alertThreshold; + }, + }, + memoryAlertThreshold: { + type: Number, + default: function () { + return this.alertThreshold; + }, + }, + diskAlertThreshold: { + type: Number, + default: function () { + return this.alertThreshold; + }, + }, }, { timestamps: true, From e52c55bf2f06e5eb09666c76683c19fba8b6d1ae Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:52:52 +0800 Subject: [PATCH 03/12] remove old treshold violated template --- Server/templates/thresholdViolated.mjml | 40 ------------------------- 1 file changed, 40 deletions(-) delete mode 100644 Server/templates/thresholdViolated.mjml diff --git a/Server/templates/thresholdViolated.mjml b/Server/templates/thresholdViolated.mjml deleted file mode 100644 index a3d4344e4..000000000 --- a/Server/templates/thresholdViolated.mjml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - {{message}} - - - {{#if cpu}} - {{cpu}} - {{/if}} - - - {{#if disk}} - {{disk}} - {{/if}} - - - {{#if memory}} - {{memory}} - {{/if}} - - - - - - \ No newline at end of file From b96648b1559bd3b2c2fdb73a58f875403760c555 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:55:31 +0800 Subject: [PATCH 04/12] add hardware monitor notification checking to jobQueue --- Server/service/jobQueue.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index eedeb1a5c..dd3ac010a 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -166,6 +166,15 @@ class JobQueue { const { monitor, statusChanged, prevStatus } = await this.statusService.updateStatus(networkResponse); + // If this is an infrastructure monitor, we need to check for thresholds + if (monitor.type === "hardware") { + await this.notificationService.handleInfrastructureNotifications({ + ...networkResponse, + monitor, + prevStatus, + }); + } + //If status hasn't changed, we're done if (statusChanged === false) return; From 355f7e0511ec04150130fe2eb392f2c722eac1a6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:56:13 +0800 Subject: [PATCH 05/12] add monitor an dprevStatus to response from statusService if monitor status does not change --- Server/service/statusService.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Server/service/statusService.js b/Server/service/statusService.js index 4f7c1a4ce..6e61ea232 100644 --- a/Server/service/statusService.js +++ b/Server/service/statusService.js @@ -29,7 +29,8 @@ class StatusService { const { monitorId, status } = networkResponse; const monitor = await this.db.getMonitorById(monitorId); // No change in monitor status, return early - if (monitor.status === status) return { statusChanged: false }; + if (monitor.status === status) + return { monitor, statusChanged: false, prevStatus: monitor.status }; // Monitor status changed, save prev status and update monitor this.logger.info({ From b9cfbd7a7485dc78817245487a4ccdc1ea479300 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:56:26 +0800 Subject: [PATCH 06/12] handle hardware notifications --- Server/service/emailService.js | 2 +- Server/service/notificationService.js | 86 ++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/Server/service/emailService.js b/Server/service/emailService.js index 5977048ef..846b7c6c8 100644 --- a/Server/service/emailService.js +++ b/Server/service/emailService.js @@ -65,7 +65,7 @@ class EmailService { serverIsDownTemplate: this.loadTemplate("serverIsDown"), serverIsUpTemplate: this.loadTemplate("serverIsUp"), passwordResetTemplate: this.loadTemplate("passwordReset"), - thresholdViolatedTemplate: this.loadTemplate("thresholdViolated"), + hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"), }; /** diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js index f464c08a7..5323f5393 100644 --- a/Server/service/notificationService.js +++ b/Server/service/notificationService.js @@ -24,8 +24,15 @@ class NotificationService { * @param {boolean} networkResponse.prevStatus - The previous status of the monitor (true for up, false for down). * @param {string} address - The email address to send the notification to. */ - async sendEmail(networkResponse, address) { + async sendEmail(networkResponse, address, alerts = []) { const { monitor, status, prevStatus } = networkResponse; + if (monitor.type === "hardware") { + const template = "hardwareIncidentTemplate"; + const context = { monitor: monitor.name, url: monitor.url, alerts }; + const subject = `Monitor ${monitor.name} infrastructure alerts`; + this.emailService.buildAndSendEmail(template, context, address, subject); + return; + } const template = prevStatus === false ? "serverIsUpTemplate" : "serverIsDownTemplate"; const context = { monitor: monitor.name, url: monitor.url }; const subject = `Monitor ${monitor.name} is ${status === true ? "up" : "down"}`; @@ -58,6 +65,83 @@ class NotificationService { }); } } + + async handleInfrastructureNotifications(networkResponse) { + const thresholds = networkResponse?.monitor?.thresholds; + if (thresholds === undefined) return; // No thresholds set, we're done + + // Get thresholds from monitor + const { + usage_cpu: cpuThreshold = -1, + usage_memory: memoryThreshold = -1, + usage_disk: diskThreshold = -1, + } = thresholds; + + // Get metrics from response + const metrics = networkResponse?.payload?.data ?? {}; + const { + cpu: { usage_percent: cpuUsage = -1 } = {}, + memory: { usage_percent: memoryUsage = -1 } = {}, + disk = [], + } = metrics; + + const alerts = { + cpu: + cpuThreshold !== -1 && cpuUsage > cpuThreshold / 100 + ? "CPU usage is above threshold" + : null, + memory: + memoryThreshold !== -1 && memoryUsage > memoryThreshold / 100 + ? "Memory usage is above threshold" + : null, + disk: disk.some( + (d) => diskThreshold !== -1 && d.usage_percent > diskThreshold / 100 + ) + ? "Disk usage is above threshold" + : null, + }; + + const notifications = await this.db.getNotificationsByMonitorId( + networkResponse.monitorId + ); + for (const notification of notifications) { + const alertsToSend = []; + const alertTypes = ["cpu", "memory", "disk"]; + + for (const type of alertTypes) { + // Iterate over each alert type to see if any need to be decmremented + if (alerts[type] !== null) { + notification[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered + + if (notification[`${type}AlertThreshold`] <= 0) { + // If threshold drops below 0, reset and send notification + notification[`${type}AlertThreshold`] = notification.alertThreshold; + + const formatAlert = { + cpu: () => + `${alerts.cpu} ${(cpuUsage * 100).toFixed(0)}% (${cpuThreshold}%)`, + memory: () => + `${alerts.memory} ${(memoryUsage * 100).toFixed(0)}% (${memoryThreshold}%)`, + disk: () => + `${alerts.disk} ${disk + .map((d, idx) => `Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%`) + .join(", ")} (${diskThreshold}%)`, + }; + + alertsToSend.push(formatAlert[type]()); + } + } + } + + await notification.save(); + + if (alertsToSend.length === 0) continue; // No alerts to send, we're done + + if (notification.type === "email") { + this.sendEmail(networkResponse, notification.address, alertsToSend); + } + } + } } export default NotificationService; From 6337cd6462c3ea09e353b847578b30a09f347784 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 12:23:24 +0800 Subject: [PATCH 07/12] convert percentage to decimal before submitting --- Client/src/Pages/Infrastructure/CreateMonitor/index.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx index 6a7081d9c..352f57bce 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx +++ b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx @@ -132,7 +132,7 @@ const CreateInfrastructureMonitor = () => { Object.keys(form) .filter((k) => k.startsWith(THRESHOLD_FIELD_PREFIX)) .map((k) => { - if (form[k]) thresholds[k] = form[k]; + if (form[k]) thresholds[k] = form[k] / 100; delete form[k]; delete form[k.substring(THRESHOLD_FIELD_PREFIX.length)]; }); @@ -158,6 +158,7 @@ const CreateInfrastructureMonitor = () => { : infrastructureMonitor.name, interval: infrastructureMonitor.interval * MS_PER_MINUTE, }; + delete form.notifications; if (hasValidationErrors(form, infrastructureMonitorValidation, setErrors)) { return; From a5e918e0a7aa5f78785db41892d6b1dcd268e1e9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 12:23:40 +0800 Subject: [PATCH 08/12] remove percentage to decimal conversion from BE --- Server/service/notificationService.js | 32 +++++++++++---------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js index 5323f5393..dfe3afc08 100644 --- a/Server/service/notificationService.js +++ b/Server/service/notificationService.js @@ -86,19 +86,11 @@ class NotificationService { } = metrics; const alerts = { - cpu: - cpuThreshold !== -1 && cpuUsage > cpuThreshold / 100 - ? "CPU usage is above threshold" - : null, - memory: - memoryThreshold !== -1 && memoryUsage > memoryThreshold / 100 - ? "Memory usage is above threshold" - : null, - disk: disk.some( - (d) => diskThreshold !== -1 && d.usage_percent > diskThreshold / 100 - ) - ? "Disk usage is above threshold" - : null, + cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false, + memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false, + disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold) + ? true + : false, }; const notifications = await this.db.getNotificationsByMonitorId( @@ -110,7 +102,7 @@ class NotificationService { for (const type of alertTypes) { // Iterate over each alert type to see if any need to be decmremented - if (alerts[type] !== null) { + if (alerts[type] === true) { notification[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered if (notification[`${type}AlertThreshold`] <= 0) { @@ -119,13 +111,15 @@ class NotificationService { const formatAlert = { cpu: () => - `${alerts.cpu} ${(cpuUsage * 100).toFixed(0)}% (${cpuThreshold}%)`, + `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`, memory: () => - `${alerts.memory} ${(memoryUsage * 100).toFixed(0)}% (${memoryThreshold}%)`, + `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`, disk: () => - `${alerts.disk} ${disk - .map((d, idx) => `Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%`) - .join(", ")} (${diskThreshold}%)`, + `Your current disk usage: ${disk + .map((d, idx) => `(Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%)`) + .join( + ", " + )} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, }; alertsToSend.push(formatAlert[type]()); From 80763356e5f2a3b7a108181d7d8ec8f676369caa Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 12:24:26 +0800 Subject: [PATCH 09/12] fix typo in comment --- Server/service/notificationService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js index dfe3afc08..88bdc0a86 100644 --- a/Server/service/notificationService.js +++ b/Server/service/notificationService.js @@ -101,7 +101,7 @@ class NotificationService { const alertTypes = ["cpu", "memory", "disk"]; for (const type of alertTypes) { - // Iterate over each alert type to see if any need to be decmremented + // Iterate over each alert type to see if any need to be decremented if (alerts[type] === true) { notification[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered From 257cc73ab3b559e9804273c321543f5f9682d593 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 24 Nov 2024 17:39:05 +0800 Subject: [PATCH 10/12] Add safety check for destructuing payload in StatusService --- Server/service/statusService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/service/statusService.js b/Server/service/statusService.js index 6e61ea232..442329620 100644 --- a/Server/service/statusService.js +++ b/Server/service/statusService.js @@ -104,7 +104,7 @@ class StatusService { if (type === "hardware") { const { cpu, memory, disk, host } = payload?.data ?? {}; - const { errors } = payload; + const { errors } = payload?.errors ?? []; check.cpu = cpu ?? {}; check.memory = memory ?? {}; check.disk = disk ?? {}; From 02666397c9187ee75108824bca6612fd869eb6a0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 24 Nov 2024 17:43:37 +0800 Subject: [PATCH 11/12] Move all notification handling from JobQueue to NotificaitonService where it should be --- Server/service/jobQueue.js | 18 +---- Server/service/notificationService.js | 107 +++++++++++++++++++------- 2 files changed, 80 insertions(+), 45 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index dd3ac010a..182f0235a 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -165,26 +165,12 @@ class JobQueue { // Handle status change const { monitor, statusChanged, prevStatus } = await this.statusService.updateStatus(networkResponse); - - // If this is an infrastructure monitor, we need to check for thresholds - if (monitor.type === "hardware") { - await this.notificationService.handleInfrastructureNotifications({ - ...networkResponse, - monitor, - prevStatus, - }); - } - - //If status hasn't changed, we're done - if (statusChanged === false) return; - - // if prevStatus is undefined, monitor is resuming, we're done - if (prevStatus === undefined) return; - + // Handle notifications this.notificationService.handleNotifications({ ...networkResponse, monitor, prevStatus, + statusChanged, }); } catch (error) { this.logger.error({ diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js index 88bdc0a86..25bf5bfa6 100644 --- a/Server/service/notificationService.js +++ b/Server/service/notificationService.js @@ -14,48 +14,62 @@ class NotificationService { } /** - * Sends an email notification based on the network response. + * Sends an email notification for hardware infrastructure alerts * - * @param {Object} networkResponse - The response from the network monitor. - * @param {Object} networkResponse.monitor - The monitor object containing details about the monitored service. - * @param {string} networkResponse.monitor.name - The name of the monitor. - * @param {string} networkResponse.monitor.url - The URL of the monitor. - * @param {boolean} networkResponse.status - The current status of the monitor (true for up, false for down). - * @param {boolean} networkResponse.prevStatus - The previous status of the monitor (true for up, false for down). - * @param {string} address - The email address to send the notification to. + * @async + * @function sendHardwareEmail + * @param {Object} networkResponse - Response object containing monitor information + * @param {string} address - Email address to send the notification to + * @param {Array} [alerts=[]] - List of hardware alerts to include in the email + * @returns {Promise} - Indicates whether email was sent successfully + * @throws {Error} */ - async sendEmail(networkResponse, address, alerts = []) { + async sendHardwareEmail(networkResponse, address, alerts = []) { + if (alerts.length === 0) return false; + const { monitor, status, prevStatus } = networkResponse; + const template = "hardwareIncidentTemplate"; + const context = { monitor: monitor.name, url: monitor.url, alerts }; + const subject = `Monitor ${monitor.name} infrastructure alerts`; + this.emailService.buildAndSendEmail(template, context, address, subject); + return true; + } + + /** + * Sends an email notification about monitor status change + * + * @async + * @function sendEmail + * @param {Object} networkResponse - Response object containing monitor status information + * @param {string} address - Email address to send the notification to + * @returns {Promise} - Indicates email was sent successfully + */ + async sendEmail(networkResponse, address) { const { monitor, status, prevStatus } = networkResponse; - if (monitor.type === "hardware") { - const template = "hardwareIncidentTemplate"; - const context = { monitor: monitor.name, url: monitor.url, alerts }; - const subject = `Monitor ${monitor.name} infrastructure alerts`; - this.emailService.buildAndSendEmail(template, context, address, subject); - return; - } const template = prevStatus === false ? "serverIsUpTemplate" : "serverIsDownTemplate"; const context = { monitor: monitor.name, url: monitor.url }; const subject = `Monitor ${monitor.name} is ${status === true ? "up" : "down"}`; this.emailService.buildAndSendEmail(template, context, address, subject); + return true; } - /** - * Handles notifications based on the network response. - * - * @param {Object} networkResponse - The response from the network monitor. - * @param {string} networkResponse.monitorId - The ID of the monitor. - */ - async handleNotifications(networkResponse) { + async handleStatusNotifications(networkResponse) { try { + //If status hasn't changed, we're done + if (networkResponse.statusChanged === false) return false; + + // if prevStatus is undefined, monitor is resuming, we're done + if (networkResponse.prevStatus === undefined) return false; const notifications = await this.db.getNotificationsByMonitorId( networkResponse.monitorId ); + for (const notification of notifications) { if (notification.type === "email") { this.sendEmail(networkResponse, notification.address); } // Handle other types of notifications here } + return true; } catch (error) { this.logger.warn({ message: error.message, @@ -65,10 +79,18 @@ class NotificationService { }); } } - - async handleInfrastructureNotifications(networkResponse) { + /** + * Handles status change notifications for a monitor + * + * @async + * @function handleStatusNotifications + * @param {Object} networkResponse - Response object containing monitor status information + * @returns {Promise} - Indicates whether notifications were processed + * @throws {Error} + */ + async handleHardwareNotifications(networkResponse) { const thresholds = networkResponse?.monitor?.thresholds; - if (thresholds === undefined) return; // No thresholds set, we're done + if (thresholds === undefined) return false; // No thresholds set, we're done // Get thresholds from monitor const { @@ -78,7 +100,9 @@ class NotificationService { } = thresholds; // Get metrics from response - const metrics = networkResponse?.payload?.data ?? {}; + const metrics = networkResponse?.payload?.data ?? null; + if (metrics === null) return false; + const { cpu: { usage_percent: cpuUsage = -1 } = {}, memory: { usage_percent: memoryUsage = -1 } = {}, @@ -121,7 +145,6 @@ class NotificationService { ", " )} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, }; - alertsToSend.push(formatAlert[type]()); } } @@ -132,9 +155,35 @@ class NotificationService { if (alertsToSend.length === 0) continue; // No alerts to send, we're done if (notification.type === "email") { - this.sendEmail(networkResponse, notification.address, alertsToSend); + this.sendHardwareEmail(networkResponse, notification.address, alertsToSend); } } + return true; + } + + /** + * Handles notifications for different monitor types + * + * @async + * @function handleNotifications + * @param {Object} networkResponse - Response object containing monitor information + * @returns {Promise} - Indicates whether notifications were processed successfully + */ + async handleNotifications(networkResponse) { + try { + if (networkResponse.monitor.type === "hardware") { + this.handleHardwareNotifications(networkResponse); + } + this.handleStatusNotifications(networkResponse); + return true; + } catch (error) { + this.logger.warn({ + message: error.message, + service: this.SERVICE_NAME, + method: "handleNotifications", + stack: error.stack, + }); + } } } From b97e149d623546f5493ee2daac38d8e7a6845cf5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 24 Nov 2024 17:43:50 +0800 Subject: [PATCH 12/12] update tests --- .../services/notificationService.test.js | 202 +++++++++++++++++- 1 file changed, 197 insertions(+), 5 deletions(-) diff --git a/Server/tests/services/notificationService.test.js b/Server/tests/services/notificationService.test.js index b33bd21ed..ccfdd22ae 100644 --- a/Server/tests/services/notificationService.test.js +++ b/Server/tests/services/notificationService.test.js @@ -78,11 +78,23 @@ describe("NotificationService", () => { describe("handleNotifications", async () => { it("should handle notifications based on the network response", async () => { notificationService.sendEmail = sinon.stub(); - notificationService.db.getNotificationsByMonitorId.resolves([ - { type: "email", address: "www.google.com" }, - ]); - await notificationService.handleNotifications({ monitorId: "123" }); - expect(notificationService.sendEmail.calledOnce).to.be.true; + const res = await notificationService.handleNotifications({ + monitor: { + type: "email", + address: "www.google.com", + }, + }); + expect(res).to.be.true; + }); + it("should handle hardware notifications", async () => { + notificationService.sendEmail = sinon.stub(); + const res = await notificationService.handleNotifications({ + monitor: { + type: "hardware", + address: "www.google.com", + }, + }); + expect(res).to.be.true; }); it("should handle an error when getting notifications", async () => { @@ -92,4 +104,184 @@ describe("NotificationService", () => { expect(notificationService.logger.warn.calledOnce).to.be.true; }); }); + + describe("sendHardwareEmail", async () => { + let networkResponse, address, alerts; + beforeEach(() => { + networkResponse = { + monitor: { + name: "Test Monitor", + url: "http://test.com", + }, + status: true, + prevStatus: false, + }; + address = "test@test.com"; + alerts = ["test"]; + }); + + afterEach(() => { + sinon.restore(); + }); + it("should send an email notification with Hardware Template", async () => { + emailService.buildAndSendEmail.resolves(true); + const res = await notificationService.sendHardwareEmail( + networkResponse, + address, + alerts + ); + expect(res).to.be.true; + }); + it("should return false if no alerts are provided", async () => { + alerts = []; + emailService.buildAndSendEmail.resolves(true); + const res = await notificationService.sendHardwareEmail( + networkResponse, + address, + alerts + ); + expect(res).to.be.false; + }); + }); + describe("handleStatusNotifications", async () => { + let networkResponse; + beforeEach(() => { + networkResponse = { + monitor: { + name: "Test Monitor", + url: "http://test.com", + }, + statusChanged: true, + status: true, + prevStatus: false, + }; + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should handle status notifications", async () => { + db.getNotificationsByMonitorId.resolves([ + { type: "email", address: "test@test.com" }, + ]); + const res = await notificationService.handleStatusNotifications(networkResponse); + expect(res).to.be.true; + }); + it("should return false if status hasn't changed", async () => { + networkResponse.statusChanged = false; + const res = await notificationService.handleStatusNotifications(networkResponse); + expect(res).to.be.false; + }); + it("should return false if prevStatus is undefined", async () => { + networkResponse.prevStatus = undefined; + const res = await notificationService.handleStatusNotifications(networkResponse); + expect(res).to.be.false; + }); + it("should handle an error", async () => { + const testError = new Error("Test Error"); + db.getNotificationsByMonitorId.rejects(testError); + try { + await notificationService.handleStatusNotifications(networkResponse); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal("Test Error"); + } + }); + }); + + describe("handleHardwareNotifications", async () => { + let networkResponse; + beforeEach(() => { + networkResponse = { + monitor: { + name: "Test Monitor", + url: "http://test.com", + thresholds: { + usage_cpu: 1, + usage_memory: 1, + usage_disk: 1, + }, + }, + payload: { + data: { + cpu: { + usage_percent: 0.655, + }, + memory: { + usage_percent: 0.783, + }, + disk: [ + { + name: "/dev/sda1", + usage_percent: 0.452, + }, + { + name: "/dev/sdb1", + usage_percent: 0.627, + }, + ], + }, + }, + }; + }); + afterEach(() => { + sinon.restore(); + }); + + describe("it should return false if no thresholds are set", () => { + it("should return false if no thresholds are set", async () => { + networkResponse.monitor.thresholds = undefined; + const res = + await notificationService.handleHardwareNotifications(networkResponse); + expect(res).to.be.false; + }); + + it("should return false if metrics are null", async () => { + networkResponse.payload.data = null; + const res = + await notificationService.handleHardwareNotifications(networkResponse); + expect(res).to.be.false; + }); + + it("should return true if request is well formed and thresholds > 0", async () => { + db.getNotificationsByMonitorId.resolves([ + { + type: "email", + address: "test@test.com", + alertThreshold: 1, + cpuAlertThreshold: 1, + memoryAlertThreshold: 1, + diskAlertThreshold: 1, + save: sinon.stub().resolves(), + }, + ]); + const res = + await notificationService.handleHardwareNotifications(networkResponse); + expect(res).to.be.true; + }); + + it("should return true if thresholds are exceeded", async () => { + db.getNotificationsByMonitorId.resolves([ + { + type: "email", + address: "test@test.com", + alertThreshold: 1, + cpuAlertThreshold: 1, + memoryAlertThreshold: 1, + diskAlertThreshold: 1, + save: sinon.stub().resolves(), + }, + ]); + networkResponse.monitor.thresholds = { + usage_cpu: 0.01, + usage_memory: 0.01, + usage_disk: 0.01, + }; + const res = + await notificationService.handleHardwareNotifications(networkResponse); + expect(res).to.be.true; + }); + }); + }); });