Merge pull request #983 from bluewave-labs/feat/be/network-service-tests
Feat/be/network service tests
This commit is contained in:
4 files changed
+809
-31
No files matched your search
+6
-1
@@ -20,7 +20,12 @@ import { fileURLToPath } from "url";
|
||||
import { connectDbAndRunServer } from "./configs/db.js";
|
||||
import queueRouter from "./routes/queueRoute.js";
|
||||
import JobQueue from "./service/jobQueue.js";
|
||||
|
||||
//Network service and dependencies
|
||||
import NetworkService from "./service/networkService.js";
|
||||
import axios from "axios";
|
||||
import ping from "ping";
|
||||
import http from "http";
|
||||
|
||||
// Email service and dependencies
|
||||
import EmailService from "./service/emailService.js";
|
||||
@@ -149,7 +154,7 @@ const startApp = async () => {
|
||||
nodemailer,
|
||||
logger
|
||||
);
|
||||
const networkService = new NetworkService(db, emailService);
|
||||
const networkService = new NetworkService(db, emailService, axios, ping, logger, http);
|
||||
const jobQueue = await JobQueue.createJobQueue(db, networkService, settingsService);
|
||||
|
||||
const cleanup = async () => {
|
||||
|
||||
@@ -98,7 +98,7 @@ class JobQueue {
|
||||
}, false);
|
||||
|
||||
if (!maintenanceWindowActive) {
|
||||
const res = await this.networkService.getStatus(job);
|
||||
await this.networkService.getStatus(job);
|
||||
} else {
|
||||
logger.info(`Monitor ${monitorId} is in maintenance window`, {
|
||||
service: SERVICE_NAME,
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import axios from "axios";
|
||||
import ping from "ping";
|
||||
import logger from "../utils/logger.js";
|
||||
import http from "http";
|
||||
import { errorMessages, successMessages } from "../utils/messages.js";
|
||||
|
||||
/**
|
||||
@@ -12,7 +8,17 @@ import { errorMessages, successMessages } from "../utils/messages.js";
|
||||
*/
|
||||
|
||||
class NetworkService {
|
||||
constructor(db, emailService) {
|
||||
/**
|
||||
* Creates an instance of NetworkService.
|
||||
*
|
||||
* @param {Object} db - The database service.
|
||||
* @param {Object} emailService - The email service.
|
||||
* @param {Object} axios - The axios HTTP client.
|
||||
* @param {Object} ping - The ping service.
|
||||
* @param {Object} logger - The logging service.
|
||||
* @param {Object} http - The HTTP service.
|
||||
*/
|
||||
constructor(db, emailService, axios, ping, logger, http) {
|
||||
this.db = db;
|
||||
this.emailService = emailService;
|
||||
this.TYPE_PING = "ping";
|
||||
@@ -20,9 +26,19 @@ class NetworkService {
|
||||
this.TYPE_PAGESPEED = "pagespeed";
|
||||
this.SERVICE_NAME = "NetworkService";
|
||||
this.NETWORK_ERROR = 5000;
|
||||
this.axios = axios;
|
||||
this.ping = ping;
|
||||
this.logger = logger;
|
||||
this.http = http;
|
||||
}
|
||||
|
||||
async handleNotification(monitor, isAlive) {
|
||||
/**
|
||||
* Handles the notification process for a monitor.
|
||||
*
|
||||
* @param {Object} monitor - The monitor object containing monitor details.
|
||||
* @param {boolean} isAlive - The status of the monitor (true if up, false if down).
|
||||
* @returns {Promise<void>}
|
||||
*/ async handleNotification(monitor, isAlive) {
|
||||
try {
|
||||
let template = isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate";
|
||||
let status = isAlive === true ? "up" : "down";
|
||||
@@ -39,7 +55,7 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error.message, {
|
||||
this.logger.error(error.message, {
|
||||
method: "handleNotification",
|
||||
service: this.SERVICE_NAME,
|
||||
monitorId: monitor._id,
|
||||
@@ -47,11 +63,19 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the status update for a monitor job.
|
||||
*
|
||||
* @param {Object} job - The job object containing job details.
|
||||
* @param {boolean} isAlive - The status of the monitor (true if up, false if down).
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async handleStatusUpdate(job, isAlive) {
|
||||
let monitor;
|
||||
const { _id } = job.data;
|
||||
|
||||
// Look up the monitor, if it doesn't exist, it's probably been removed, return
|
||||
try {
|
||||
const { _id } = job.data;
|
||||
monitor = await this.db.getMonitorById(_id);
|
||||
} catch (error) {
|
||||
return;
|
||||
@@ -60,7 +84,7 @@ class NetworkService {
|
||||
// Otherwise, try to update monitor status
|
||||
try {
|
||||
if (monitor === null || monitor === undefined) {
|
||||
logger.error(`Null Monitor: ${_id}`, {
|
||||
this.logger.error(`Null Monitor: ${_id}`, {
|
||||
method: "handleStatusUpdate",
|
||||
service: this.SERVICE_NAME,
|
||||
jobId: job.id,
|
||||
@@ -77,7 +101,7 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error.message, {
|
||||
this.logger.error(error.message, {
|
||||
method: "handleStatusUpdate",
|
||||
service: this.SERVICE_NAME,
|
||||
jobId: job.id,
|
||||
@@ -100,7 +124,7 @@ class NetworkService {
|
||||
} catch (error) {
|
||||
const endTime = Date.now();
|
||||
error.responseTime = endTime - startTime;
|
||||
error.service === undefined ? (error.service = SERVICE_NAME) : null;
|
||||
error.service === undefined ? (error.service = this.SERVICE_NAME) : null;
|
||||
error.method === undefined ? (error.method = "measureResponseTime") : null;
|
||||
throw error;
|
||||
}
|
||||
@@ -112,13 +136,13 @@ class NetworkService {
|
||||
* @returns {Promise<{boolean}} The result of logging and storing the check
|
||||
*/
|
||||
async handlePing(job) {
|
||||
let isAlive;
|
||||
|
||||
const operation = async () => {
|
||||
const response = await ping.promise.probe(job.data.url);
|
||||
const response = await this.ping.promise.probe(job.data.url);
|
||||
return response;
|
||||
};
|
||||
|
||||
let isAlive;
|
||||
|
||||
try {
|
||||
const { responseTime, response } = await this.measureResponseTime(operation);
|
||||
isAlive = response.alive;
|
||||
@@ -130,7 +154,7 @@ class NetworkService {
|
||||
? successMessages.PING_SUCCESS
|
||||
: errorMessages.PING_CANNOT_RESOLVE,
|
||||
};
|
||||
return await this.logAndStoreCheck(checkData, this.db.createCheck);
|
||||
await this.logAndStoreCheck(checkData, this.db.createCheck);
|
||||
} catch (error) {
|
||||
isAlive = false;
|
||||
const checkData = {
|
||||
@@ -139,7 +163,7 @@ class NetworkService {
|
||||
message: errorMessages.PING_CANNOT_RESOLVE,
|
||||
responseTime: error.responseTime,
|
||||
};
|
||||
return await this.logAndStoreCheck(checkData, this.db.createCheck);
|
||||
await this.logAndStoreCheck(checkData, this.db.createCheck);
|
||||
} finally {
|
||||
this.handleStatusUpdate(job, isAlive);
|
||||
}
|
||||
@@ -153,7 +177,7 @@ class NetworkService {
|
||||
async handleHttp(job) {
|
||||
// Define operation for timing
|
||||
const operation = async () => {
|
||||
const response = await axios.get(job.data.url);
|
||||
const response = await this.axios.get(job.data.url);
|
||||
return response;
|
||||
};
|
||||
|
||||
@@ -171,12 +195,12 @@ class NetworkService {
|
||||
status: isAlive,
|
||||
responseTime,
|
||||
statusCode: response.status,
|
||||
message: http.STATUS_CODES[response.status],
|
||||
message: this.http.STATUS_CODES[response.status],
|
||||
};
|
||||
return await this.logAndStoreCheck(checkData, this.db.createCheck);
|
||||
await this.logAndStoreCheck(checkData, this.db.createCheck);
|
||||
} catch (error) {
|
||||
const statusCode = error.response?.status || this.NETWORK_ERROR;
|
||||
let message = http.STATUS_CODES[statusCode] || "Network Error";
|
||||
let message = this.http.STATUS_CODES[statusCode] || "Network Error";
|
||||
isAlive = false;
|
||||
const checkData = {
|
||||
monitorId: job.data._id,
|
||||
@@ -185,8 +209,7 @@ class NetworkService {
|
||||
responseTime: error.responseTime,
|
||||
message,
|
||||
};
|
||||
|
||||
return await this.logAndStoreCheck(checkData, this.db.createCheck);
|
||||
await this.logAndStoreCheck(checkData, this.db.createCheck);
|
||||
} finally {
|
||||
this.handleStatusUpdate(job, isAlive);
|
||||
}
|
||||
@@ -210,7 +233,8 @@ class NetworkService {
|
||||
let isAlive;
|
||||
try {
|
||||
const url = job.data.url;
|
||||
const response = await axios.get(
|
||||
|
||||
const response = await this.axios.get(
|
||||
`https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`
|
||||
);
|
||||
const pageSpeedResults = response.data;
|
||||
@@ -223,7 +247,6 @@ class NetworkService {
|
||||
"largest-contentful-paint": lcp,
|
||||
"total-blocking-time": tbt,
|
||||
} = audits;
|
||||
|
||||
// Weights
|
||||
// First Contentful Paint 10%
|
||||
// Speed Index 10%
|
||||
@@ -236,7 +259,7 @@ class NetworkService {
|
||||
monitorId: job.data._id,
|
||||
status: isAlive,
|
||||
statusCode: response.status,
|
||||
message: http.STATUS_CODES[response.status],
|
||||
message: this.http.STATUS_CODES[response.status],
|
||||
accessibility: (categories.accessibility?.score || 0) * 100,
|
||||
bestPractices: (categories["best-practices"]?.score || 0) * 100,
|
||||
seo: (categories.seo?.score || 0) * 100,
|
||||
@@ -249,12 +272,11 @@ class NetworkService {
|
||||
tbt,
|
||||
},
|
||||
};
|
||||
|
||||
this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck);
|
||||
} catch (error) {
|
||||
isAlive = false;
|
||||
const statusCode = error.response?.status || this.NETWORK_ERROR;
|
||||
const message = http.STATUS_CODES[statusCode] || "Network Error";
|
||||
const message = this.http.STATUS_CODES[statusCode] || "Network Error";
|
||||
const checkData = {
|
||||
monitorId: job.data._id,
|
||||
status: isAlive,
|
||||
@@ -287,7 +309,7 @@ class NetworkService {
|
||||
case this.TYPE_PAGESPEED:
|
||||
return await this.handlePagespeed(job);
|
||||
default:
|
||||
logger.error(`Unsupported type: ${job.data.type}`, {
|
||||
this.logger.error(`Unsupported type: ${job.data.type}`, {
|
||||
service: this.SERVICE_NAME,
|
||||
method: "getStatus",
|
||||
jobId: job.id,
|
||||
@@ -311,8 +333,9 @@ class NetworkService {
|
||||
if (insertedCheck !== null && insertedCheck !== undefined) {
|
||||
return insertedCheck.status;
|
||||
}
|
||||
throw new Error();
|
||||
} catch (error) {
|
||||
logger.error(`Error wrtiting check for ${data.monitorId}`, {
|
||||
this.logger.error(`Error writing check for ${data.monitorId}`, {
|
||||
service: this.SERVICE_NAME,
|
||||
method: "logAndStoreCheck",
|
||||
monitorId: data.monitorId,
|
||||
|
||||
@@ -0,0 +1,750 @@
|
||||
import sinon from "sinon";
|
||||
import NetworkService from "../../service/networkService.js";
|
||||
import { errorMessages, successMessages } from "../../utils/messages.js";
|
||||
import exp from "constants";
|
||||
describe("NetworkService - Constructor", function () {
|
||||
let dbMock, emailServiceMock, axiosMock, pingMock, loggerMock, httpMock;
|
||||
|
||||
beforeEach(function () {
|
||||
dbMock = sinon.stub();
|
||||
emailServiceMock = sinon.stub();
|
||||
axiosMock = sinon.stub();
|
||||
pingMock = sinon.stub();
|
||||
loggerMock = sinon.stub();
|
||||
httpMock = sinon.stub();
|
||||
});
|
||||
|
||||
it("should correctly initialize properties", function () {
|
||||
const networkService = new NetworkService(
|
||||
dbMock,
|
||||
emailServiceMock,
|
||||
axiosMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock
|
||||
);
|
||||
|
||||
expect(networkService.db).to.equal(dbMock);
|
||||
expect(networkService.emailService).to.equal(emailServiceMock);
|
||||
expect(networkService.TYPE_PING).to.equal("ping");
|
||||
expect(networkService.TYPE_HTTP).to.equal("http");
|
||||
expect(networkService.TYPE_PAGESPEED).to.equal("pagespeed");
|
||||
expect(networkService.SERVICE_NAME).to.equal("NetworkService");
|
||||
expect(networkService.NETWORK_ERROR).to.equal(5000);
|
||||
expect(networkService.axios).to.equal(axiosMock);
|
||||
expect(networkService.ping).to.equal(pingMock);
|
||||
expect(networkService.logger).to.equal(loggerMock);
|
||||
expect(networkService.http).to.equal(httpMock);
|
||||
});
|
||||
});
|
||||
|
||||
describe("NetworkService - handleNotification", () => {
|
||||
let dbMock, emailServiceMock, loggerMock, networkService;
|
||||
|
||||
beforeEach(function () {
|
||||
dbMock = {
|
||||
getNotificationsByMonitorId: sinon.stub(),
|
||||
};
|
||||
emailServiceMock = {
|
||||
buildAndSendEmail: sinon.stub().resolves(),
|
||||
};
|
||||
loggerMock = {
|
||||
error: sinon.stub(),
|
||||
};
|
||||
|
||||
networkService = new NetworkService(
|
||||
dbMock,
|
||||
emailServiceMock,
|
||||
null,
|
||||
null,
|
||||
loggerMock,
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
it("should send email notifications when monitor is down", async function () {
|
||||
const monitor = { _id: "monitor1", name: "Test Monitor", url: "http://test.com" };
|
||||
const notifications = [{ type: "email", address: "test@example.com" }];
|
||||
dbMock.getNotificationsByMonitorId.resolves(notifications);
|
||||
|
||||
await networkService.handleNotification(monitor, false);
|
||||
|
||||
expect(emailServiceMock.buildAndSendEmail.calledOnce).to.be.true;
|
||||
expect(
|
||||
emailServiceMock.buildAndSendEmail.calledWith(
|
||||
"serverIsDownTemplate",
|
||||
{ monitorName: monitor.name, monitorUrl: monitor.url },
|
||||
"test@example.com",
|
||||
"Monitor Test Monitor is down"
|
||||
)
|
||||
).to.be.true;
|
||||
});
|
||||
|
||||
it("should send email notifications when monitor is up", async function () {
|
||||
const monitor = { _id: "monitor1", name: "Test Monitor", url: "http://test.com" };
|
||||
const notifications = [{ type: "email", address: "test@example.com" }];
|
||||
dbMock.getNotificationsByMonitorId.resolves(notifications);
|
||||
|
||||
await networkService.handleNotification(monitor, true);
|
||||
|
||||
expect(emailServiceMock.buildAndSendEmail.calledOnce).to.be.true;
|
||||
expect(
|
||||
emailServiceMock.buildAndSendEmail.calledWith(
|
||||
"serverIsUpTemplate",
|
||||
{ monitorName: monitor.name, monitorUrl: monitor.url },
|
||||
"test@example.com",
|
||||
"Monitor Test Monitor is up"
|
||||
)
|
||||
).to.be.true;
|
||||
});
|
||||
|
||||
it("should log an error if an exception is thrown", async function () {
|
||||
const monitor = { _id: "monitor1", name: "Test Monitor", url: "http://test.com" };
|
||||
dbMock.getNotificationsByMonitorId.rejects(new Error("Database error"));
|
||||
|
||||
await networkService.handleNotification(monitor, true);
|
||||
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(
|
||||
loggerMock.error.calledWith("Database error", {
|
||||
method: "handleNotification",
|
||||
service: networkService.SERVICE_NAME,
|
||||
monitorId: monitor._id,
|
||||
})
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("NetworkService - handleStatusUpdate", function () {
|
||||
let dbMock,
|
||||
emailServiceMock,
|
||||
axiosMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock,
|
||||
networkService,
|
||||
monitorMock;
|
||||
|
||||
beforeEach(function () {
|
||||
dbMock = { getMonitorById: sinon.stub() };
|
||||
emailServiceMock = sinon.stub();
|
||||
axiosMock = sinon.stub();
|
||||
pingMock = sinon.stub();
|
||||
loggerMock = { error: sinon.stub() };
|
||||
httpMock = sinon.stub();
|
||||
networkService = new NetworkService(
|
||||
dbMock,
|
||||
emailServiceMock,
|
||||
axiosMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock
|
||||
);
|
||||
monitorMock = {
|
||||
status: sinon.stub(),
|
||||
save: sinon.stub(),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should return if getMonitorById throws an error", async function () {
|
||||
dbMock.getMonitorById.throws(new Error("Database error"));
|
||||
|
||||
const res = await networkService.handleStatusUpdate(
|
||||
{ data: { _id: "monitor1" } },
|
||||
true
|
||||
);
|
||||
expect(res).to.be.undefined;
|
||||
});
|
||||
|
||||
it("should log an error if monitor is null", async function () {
|
||||
dbMock.getMonitorById.resolves(null);
|
||||
|
||||
await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true);
|
||||
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith("Null Monitor: monitor1")).to.be.true;
|
||||
});
|
||||
|
||||
it("should update monitor status if different", async function () {
|
||||
monitorMock.status = false;
|
||||
dbMock.getMonitorById.resolves(monitorMock);
|
||||
|
||||
await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true);
|
||||
|
||||
expect(monitorMock.save.calledOnce).to.be.true;
|
||||
expect(monitorMock.status).to.be.true;
|
||||
});
|
||||
|
||||
it("should not update monitor status if same", async function () {
|
||||
monitorMock.status = true;
|
||||
dbMock.getMonitorById.resolves(monitorMock);
|
||||
|
||||
await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true);
|
||||
|
||||
expect(monitorMock.save.called).to.be.false;
|
||||
});
|
||||
|
||||
it("should log an error if exception is thrown during status update", async function () {
|
||||
monitorMock.status = false;
|
||||
dbMock.getMonitorById.resolves(monitorMock);
|
||||
monitorMock.save.rejects(new Error("Save error"));
|
||||
|
||||
await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true);
|
||||
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith("Save error")).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("NetworkService - measureResponseTime", () => {
|
||||
let mockOperation,
|
||||
dbMock,
|
||||
emailServiceMock,
|
||||
axiosMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock,
|
||||
networkService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockOperation = sinon.stub();
|
||||
dbMock = { getMonitorById: sinon.stub() };
|
||||
emailServiceMock = sinon.stub();
|
||||
axiosMock = sinon.stub();
|
||||
pingMock = sinon.stub();
|
||||
loggerMock = { error: sinon.stub() };
|
||||
httpMock = sinon.stub();
|
||||
networkService = new NetworkService(
|
||||
dbMock,
|
||||
emailServiceMock,
|
||||
axiosMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should return response time and response on successful operation", async () => {
|
||||
const fakeResponse = { data: "test" };
|
||||
mockOperation.resolves(fakeResponse);
|
||||
|
||||
const result = await networkService.measureResponseTime(mockOperation);
|
||||
expect(result).to.have.property("responseTime").that.is.a("number");
|
||||
expect(result).to.have.property("response").that.deep.equals(fakeResponse);
|
||||
});
|
||||
|
||||
it("should throw an error with response time and additional properties on operation failure", async () => {
|
||||
const fakeError = new Error("Operation failed");
|
||||
mockOperation.rejects(fakeError);
|
||||
|
||||
try {
|
||||
await networkService.measureResponseTime(mockOperation);
|
||||
} catch (error) {
|
||||
expect(error).to.have.property("responseTime").that.is.a("number");
|
||||
expect(error).to.have.property("service", networkService.SERVICE_NAME);
|
||||
expect(error).to.have.property("method", "measureResponseTime");
|
||||
expect(error.message).to.equal("Operation failed");
|
||||
}
|
||||
});
|
||||
it("should throw an error with response time set properties on operation failure", async () => {
|
||||
const fakeError = new Error("Operation failed");
|
||||
fakeError.service = "test";
|
||||
fakeError.method = "testMethod";
|
||||
mockOperation.rejects(fakeError);
|
||||
|
||||
try {
|
||||
await networkService.measureResponseTime(mockOperation);
|
||||
} catch (error) {
|
||||
expect(error).to.have.property("responseTime").that.is.a("number");
|
||||
expect(error).to.have.property("service", "test");
|
||||
expect(error).to.have.property("method", "testMethod");
|
||||
expect(error.message).to.equal("Operation failed");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("networkService - handlePing", () => {
|
||||
let job,
|
||||
dbMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock,
|
||||
networkService,
|
||||
logAndStoreCheckStub,
|
||||
handleStatusUpdateStub;
|
||||
beforeEach(function () {
|
||||
job = {
|
||||
data: {
|
||||
url: "http://example.com",
|
||||
_id: "12345",
|
||||
},
|
||||
};
|
||||
|
||||
dbMock = { getMonitorById: sinon.stub() };
|
||||
pingMock = { promise: { probe: sinon.stub() } };
|
||||
loggerMock = { error: sinon.stub() };
|
||||
httpMock = sinon.stub();
|
||||
networkService = new NetworkService(
|
||||
dbMock,
|
||||
null,
|
||||
null,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock
|
||||
);
|
||||
logAndStoreCheckStub = sinon.stub(networkService, "logAndStoreCheck");
|
||||
handleStatusUpdateStub = sinon.stub(networkService, "handleStatusUpdate");
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should handle a successful ping response", async function () {
|
||||
const response = { alive: true };
|
||||
const responseTime = 0;
|
||||
pingMock.promise.probe.resolves(response);
|
||||
logAndStoreCheckStub.resolves();
|
||||
await networkService.handlePing(job);
|
||||
expect(
|
||||
logAndStoreCheckStub.calledOnceWith(
|
||||
{
|
||||
monitorId: job.data._id,
|
||||
status: true,
|
||||
responseTime,
|
||||
message: successMessages.PING_SUCCESS,
|
||||
},
|
||||
networkService.db.createCheck
|
||||
)
|
||||
).to.be.true;
|
||||
expect(handleStatusUpdateStub.calledOnceWith(job, true)).to.be.true;
|
||||
});
|
||||
it("should handle a successful ping response and isAlive === false", async function () {
|
||||
const response = { alive: false };
|
||||
const responseTime = 0;
|
||||
pingMock.promise.probe.resolves(response);
|
||||
logAndStoreCheckStub.resolves();
|
||||
|
||||
await networkService.handlePing(job);
|
||||
console.log(logAndStoreCheckStub.getCall(0).args[0]);
|
||||
expect(
|
||||
logAndStoreCheckStub.calledOnceWith(
|
||||
{
|
||||
monitorId: job.data._id,
|
||||
status: false,
|
||||
responseTime,
|
||||
message: errorMessages.PING_CANNOT_RESOLVE,
|
||||
},
|
||||
networkService.db.createCheck
|
||||
)
|
||||
).to.be.true;
|
||||
expect(handleStatusUpdateStub.calledOnceWith(job, false)).to.be.true;
|
||||
});
|
||||
|
||||
it("should handle a failed ping response", async function () {
|
||||
const error = new Error("Ping failed");
|
||||
error.responseTime = 0;
|
||||
|
||||
pingMock.promise.probe.rejects(error);
|
||||
|
||||
await networkService.handlePing(job);
|
||||
|
||||
expect(handleStatusUpdateStub.calledOnceWith(job, false)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("NetworkService - handleHttp", () => {
|
||||
let networkService,
|
||||
axiosMock,
|
||||
dbMock,
|
||||
httpMock,
|
||||
jobMock,
|
||||
loggerMock,
|
||||
logAndStoreCheckStub,
|
||||
handleStatusUpdateStub;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosMock = {
|
||||
get: sinon.stub(),
|
||||
};
|
||||
dbMock = {
|
||||
createCheck: sinon.stub().resolves(),
|
||||
};
|
||||
httpMock = {
|
||||
STATUS_CODES: {
|
||||
200: "OK",
|
||||
500: "Internal Server Error",
|
||||
},
|
||||
};
|
||||
loggerMock = {
|
||||
error: sinon.stub(),
|
||||
};
|
||||
networkService = new NetworkService(
|
||||
dbMock,
|
||||
null,
|
||||
axiosMock,
|
||||
null,
|
||||
loggerMock,
|
||||
httpMock
|
||||
);
|
||||
jobMock = {
|
||||
data: {
|
||||
url: "http://example.com",
|
||||
_id: "monitorId123",
|
||||
},
|
||||
};
|
||||
sinon.stub(networkService, "measureResponseTime").callsFake(async (operation) => {
|
||||
const response = await operation();
|
||||
return { responseTime: 100, response };
|
||||
});
|
||||
logAndStoreCheckStub = sinon.stub(networkService, "logAndStoreCheck").resolves();
|
||||
handleStatusUpdateStub = sinon.stub(networkService, "handleStatusUpdate").resolves();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should handle a successful HTTP response", async () => {
|
||||
const responseMock = { status: 200 };
|
||||
axiosMock.get.resolves(responseMock);
|
||||
|
||||
await networkService.handleHttp(jobMock);
|
||||
|
||||
expect(networkService.logAndStoreCheck.calledOnce).to.be.true;
|
||||
const checkData = networkService.logAndStoreCheck.getCall(0).args[0];
|
||||
expect(checkData).to.include({
|
||||
monitorId: jobMock.data._id,
|
||||
status: true,
|
||||
statusCode: 200,
|
||||
message: "OK",
|
||||
});
|
||||
expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, true)).to.be.true;
|
||||
});
|
||||
|
||||
it("should handle an HTTP error response", async () => {
|
||||
const errorMock = { response: { status: 500 }, responseTime: 200 };
|
||||
axiosMock.get.rejects(errorMock);
|
||||
|
||||
await networkService.handleHttp(jobMock);
|
||||
|
||||
expect(networkService.logAndStoreCheck.calledOnce).to.be.true;
|
||||
const checkData = networkService.logAndStoreCheck.getCall(0).args[0];
|
||||
expect(checkData).to.include({
|
||||
monitorId: jobMock.data._id,
|
||||
status: false,
|
||||
statusCode: 500,
|
||||
message: "Internal Server Error",
|
||||
});
|
||||
expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, false)).to.be.true;
|
||||
});
|
||||
it("should handle an HTTP error response with undefined status", async () => {
|
||||
const errorMock = { response: { status: undefined }, responseTime: 200 };
|
||||
axiosMock.get.rejects(errorMock);
|
||||
|
||||
await networkService.handleHttp(jobMock);
|
||||
|
||||
expect(networkService.logAndStoreCheck.calledOnce).to.be.true;
|
||||
const checkData = networkService.logAndStoreCheck.getCall(0).args[0];
|
||||
expect(checkData).to.include({
|
||||
monitorId: jobMock.data._id,
|
||||
status: false,
|
||||
statusCode: 5000,
|
||||
message: "Network Error",
|
||||
});
|
||||
expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, false)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("networkService - handlePagespeed", () => {
|
||||
let dbMock,
|
||||
axiosMock,
|
||||
jobMock,
|
||||
emailServiceMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock,
|
||||
networkService,
|
||||
logAndStoreCheckStub,
|
||||
handleStatusUpdateStub;
|
||||
beforeEach(() => {
|
||||
jobMock = {
|
||||
data: {
|
||||
_id: "12345",
|
||||
url: "http://example.com",
|
||||
},
|
||||
};
|
||||
dbMock = { getMonitorById: sinon.stub() };
|
||||
axiosMock = { get: sinon.stub() };
|
||||
|
||||
emailServiceMock = sinon.stub();
|
||||
pingMock = { promise: { probe: sinon.stub() } };
|
||||
loggerMock = { error: sinon.stub() };
|
||||
httpMock = {
|
||||
STATUS_CODES: {
|
||||
200: "OK",
|
||||
500: "Internal Server Error",
|
||||
},
|
||||
};
|
||||
networkService = new NetworkService(
|
||||
dbMock,
|
||||
emailServiceMock,
|
||||
axiosMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock
|
||||
);
|
||||
logAndStoreCheckStub = sinon.stub(networkService, "logAndStoreCheck").resolves();
|
||||
handleStatusUpdateStub = sinon.stub(networkService, "handleStatusUpdate").resolves();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should handle a successful PageSpeed response", async () => {
|
||||
const responseMock = {
|
||||
status: 200,
|
||||
data: {
|
||||
lighthouseResult: {
|
||||
categories: {
|
||||
accessibility: { score: 0.9 },
|
||||
"best-practices": { score: 0.8 },
|
||||
seo: { score: 0.7 },
|
||||
performance: { score: 0.6 },
|
||||
},
|
||||
audits: {
|
||||
"cumulative-layout-shift": { score: 0.1 },
|
||||
"speed-index": { score: 0.2 },
|
||||
"first-contentful-paint": { score: 0.3 },
|
||||
"largest-contentful-paint": { score: 0.4 },
|
||||
"total-blocking-time": { score: 0.5 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
axiosMock.get.resolves(responseMock);
|
||||
|
||||
await networkService.handlePagespeed(jobMock);
|
||||
expect(networkService.logAndStoreCheck.calledOnce).to.be.true;
|
||||
const checkData = networkService.logAndStoreCheck.getCall(0).args[0];
|
||||
expect(checkData).to.include({
|
||||
monitorId: jobMock.data._id,
|
||||
status: true,
|
||||
statusCode: 200,
|
||||
message: "OK",
|
||||
accessibility: 90,
|
||||
bestPractices: 80,
|
||||
seo: 70,
|
||||
performance: 60,
|
||||
});
|
||||
expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, true)).to.be.true;
|
||||
});
|
||||
|
||||
it("should handle a successful PageSpeed response with missing data", async () => {
|
||||
const responseMock = {
|
||||
status: 200,
|
||||
data: {
|
||||
lighthouseResult: {
|
||||
categories: {},
|
||||
audits: {
|
||||
"cumulative-layout-shift": { score: 0.1 },
|
||||
"speed-index": { score: 0.2 },
|
||||
"first-contentful-paint": { score: 0.3 },
|
||||
"largest-contentful-paint": { score: 0.4 },
|
||||
"total-blocking-time": { score: 0.5 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
axiosMock.get.resolves(responseMock);
|
||||
|
||||
await networkService.handlePagespeed(jobMock);
|
||||
expect(networkService.logAndStoreCheck.calledOnce).to.be.true;
|
||||
const checkData = networkService.logAndStoreCheck.getCall(0).args[0];
|
||||
expect(checkData).to.include({
|
||||
monitorId: jobMock.data._id,
|
||||
status: true,
|
||||
statusCode: 200,
|
||||
message: "OK",
|
||||
accessibility: 0,
|
||||
bestPractices: 0,
|
||||
seo: 0,
|
||||
performance: 0,
|
||||
});
|
||||
expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, true)).to.be.true;
|
||||
});
|
||||
|
||||
it("should handle an error PageSpeed response", async () => {
|
||||
const errorMock = { response: { status: 500 } };
|
||||
axiosMock.get.rejects(errorMock);
|
||||
|
||||
await networkService.handlePagespeed(jobMock);
|
||||
|
||||
expect(networkService.logAndStoreCheck.calledOnce).to.be.true;
|
||||
const checkData = networkService.logAndStoreCheck.getCall(0).args[0];
|
||||
expect(checkData).to.include({
|
||||
monitorId: jobMock.data._id,
|
||||
status: false,
|
||||
statusCode: 500,
|
||||
message: "Internal Server Error",
|
||||
accessibility: 0,
|
||||
bestPractices: 0,
|
||||
seo: 0,
|
||||
performance: 0,
|
||||
});
|
||||
expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, false)).to.be.true;
|
||||
});
|
||||
it("should handle an error PageSpeed response with unknown status", async () => {
|
||||
const errorMock = { response: { status: undefined } };
|
||||
axiosMock.get.rejects(errorMock);
|
||||
|
||||
await networkService.handlePagespeed(jobMock);
|
||||
|
||||
expect(networkService.logAndStoreCheck.calledOnce).to.be.true;
|
||||
const checkData = networkService.logAndStoreCheck.getCall(0).args[0];
|
||||
expect(checkData).to.include({
|
||||
monitorId: jobMock.data._id,
|
||||
status: false,
|
||||
statusCode: 5000,
|
||||
message: "Network Error",
|
||||
accessibility: 0,
|
||||
bestPractices: 0,
|
||||
seo: 0,
|
||||
performance: 0,
|
||||
});
|
||||
expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, false)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("NetworkService - getStatus", () => {
|
||||
let dbMock, emailServiceMock, axiosMock, pingMock, loggerMock, httpMock, networkService;
|
||||
|
||||
beforeEach(() => {
|
||||
dbMock = { getMonitorById: sinon.stub() };
|
||||
emailServiceMock = sinon.stub();
|
||||
axiosMock = sinon.stub();
|
||||
pingMock = sinon.stub();
|
||||
loggerMock = { error: sinon.stub() };
|
||||
httpMock = sinon.stub();
|
||||
networkService = new NetworkService(
|
||||
dbMock,
|
||||
emailServiceMock,
|
||||
axiosMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should return true if the job type is ping and handlePing is successful", async () => {
|
||||
const job = { data: { type: networkService.TYPE_PING } };
|
||||
sinon.stub(networkService, "handlePing").resolves(true);
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.true;
|
||||
});
|
||||
it("should return false if the job type is ping and handlePing is not successful", async () => {
|
||||
const job = { data: { type: networkService.TYPE_PING } };
|
||||
sinon.stub(networkService, "handlePing").resolves(false);
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
it("should return true if the job type is http and handleHttp is successful", async () => {
|
||||
const job = { data: { type: networkService.TYPE_HTTP } };
|
||||
sinon.stub(networkService, "handleHttp").resolves(true);
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.true;
|
||||
});
|
||||
it("should return false if the job type is http and handleHttp is not successful", async () => {
|
||||
const job = { data: { type: networkService.TYPE_HTTP } };
|
||||
sinon.stub(networkService, "handleHttp").resolves(false);
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
it("should return true if the job type is pagespeed and handlePagespeed is successful", async () => {
|
||||
const job = { data: { type: networkService.TYPE_PAGESPEED } };
|
||||
sinon.stub(networkService, "handlePagespeed").resolves(true);
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.true;
|
||||
});
|
||||
it("should return false if the job type is pagespeed and handlePagespeed is not successful", async () => {
|
||||
const job = { data: { type: networkService.TYPE_PAGESPEED } };
|
||||
sinon.stub(networkService, "handlePagespeed").resolves(false);
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
it("should log an error and return false if the job type is unknown", async () => {
|
||||
const job = { data: { type: "unknown" } };
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.false;
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith(`Unsupported type: unknown`)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("NetworkService - logAndStoreCheck", async () => {
|
||||
let dbMock, emailServiceMock, axiosMock, pingMock, loggerMock, httpMock, networkService;
|
||||
|
||||
beforeEach(() => {
|
||||
dbMock = { getMonitorById: sinon.stub() };
|
||||
emailServiceMock = sinon.stub();
|
||||
axiosMock = sinon.stub();
|
||||
pingMock = sinon.stub();
|
||||
loggerMock = { error: sinon.stub() };
|
||||
httpMock = sinon.stub();
|
||||
networkService = new NetworkService(
|
||||
dbMock,
|
||||
emailServiceMock,
|
||||
axiosMock,
|
||||
pingMock,
|
||||
loggerMock,
|
||||
httpMock
|
||||
);
|
||||
});
|
||||
const data = { monitorId: "12345" };
|
||||
const writeToDB = sinon.stub();
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should log an error if `writeToDb` throws an error", async () => {
|
||||
writeToDB.rejects(new Error("Database error"));
|
||||
await networkService.logAndStoreCheck(data, writeToDB);
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith(`Error writing check for ${data.monitorId}`)).to.be
|
||||
.true;
|
||||
});
|
||||
|
||||
it("should return the status of the inserted check if successful", async () => {
|
||||
writeToDB.resolves({ status: true });
|
||||
const result = await networkService.logAndStoreCheck(data, writeToDB);
|
||||
expect(result).to.be.true;
|
||||
});
|
||||
|
||||
it("should thrown an error if the check is not inserted (null)", async () => {
|
||||
writeToDB.resolves(null);
|
||||
await networkService.logAndStoreCheck(data, writeToDB);
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith(`Error writing check for ${data.monitorId}`)).to.be
|
||||
.true;
|
||||
});
|
||||
it("should thrown an error if the check is not inserted (undefined)", async () => {
|
||||
writeToDB.resolves(undefined);
|
||||
await networkService.logAndStoreCheck(data, writeToDB);
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith(`Error writing check for ${data.monitorId}`)).to.be
|
||||
.true;
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user