Files
Checkmate/server/tests/db/monitorModule.test.js
Alex Holliday bfa6832beb format
2025-07-21 10:34:16 -07:00

1866 lines
51 KiB
JavaScript
Executable File

import sinon from "sinon";
import Monitor from "../../db/models/Monitor.js";
import Check from "../../db/models/Check.js";
import PageSpeedCheck from "../../db/models/PageSpeedCheck.js";
import HardwareCheck from "../../db/models/HardwareCheck.js";
import Notification from "../../db/models/Notification.js";
import { errorMessages } from "../../utils/messages.js";
import {
getAllMonitors,
getAllMonitorsWithUptimeStats,
getMonitorStatsById,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
calculateUptimeDuration,
getLastChecked,
getLatestResponseTime,
getAverageResponseTime,
getUptimePercentage,
getIncidents,
getMonitorChecks,
processChecksForDisplay,
groupChecksByTime,
calculateGroupStats,
} from "../../db/mongo/modules/monitorModule.js";
describe("monitorModule", function () {
let monitorFindStub,
monitorFindByIdStub,
monitorFindByIdAndUpdateStub,
monitorFindByIdAndDeleteStub,
monitorDeleteManyStub,
monitorCountStub,
monitorInsertManyStub,
checkFindStub,
pageSpeedCheckFindStub,
hardwareCheckFindStub;
beforeEach(function () {
monitorFindStub = sinon.stub(Monitor, "find");
monitorFindByIdStub = sinon.stub(Monitor, "findById");
monitorFindByIdAndUpdateStub = sinon.stub(Monitor, "findByIdAndUpdate");
monitorFindByIdAndDeleteStub = sinon.stub(Monitor, "findByIdAndDelete");
monitorDeleteManyStub = sinon.stub(Monitor, "deleteMany");
monitorCountStub = sinon.stub(Monitor, "countDocuments");
monitorInsertManyStub = sinon.stub(Monitor, "insertMany");
checkFindStub = sinon.stub(Check, "find").returns({
sort: sinon.stub(),
});
pageSpeedCheckFindStub = sinon.stub(PageSpeedCheck, "find").returns({
sort: sinon.stub(),
});
hardwareCheckFindStub = sinon.stub(HardwareCheck, "find").returns({
sort: sinon.stub(),
});
});
afterEach(function () {
sinon.restore();
});
describe("getAllMonitors", function () {
it("should return all monitors", async function () {
const mockMonitors = [
{ _id: "1", name: "Monitor 1", url: "test1.com" },
{ _id: "2", name: "Monitor 2", url: "test2.com" },
];
monitorFindStub.returns(mockMonitors);
const result = await getAllMonitors();
expect(result).to.deep.equal(mockMonitors);
expect(monitorFindStub.calledOnce).to.be.true;
expect(monitorFindStub.firstCall.args).to.deep.equal([]);
});
it("should handle empty results", async function () {
monitorFindStub.returns([]);
const result = await getAllMonitors();
expect(result).to.be.an("array").that.is.empty;
});
it("should throw error when database fails", async function () {
// Arrange
const error = new Error("Database error");
error.service = "MonitorModule";
error.method = "getAllMonitors";
monitorFindStub.rejects(error);
// Act & Assert
try {
await getAllMonitors();
expect.fail("Should have thrown an error");
} catch (err) {
expect(err).to.equal(error);
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("getAllMonitors");
}
});
});
describe("getAllMonitorsWithUptimeStats", function () {
it("should return monitors with uptime stats for different time periods", async function () {
// Mock data
const mockMonitors = [
{
_id: "monitor1",
type: "http",
toObject: () => ({
_id: "monitor1",
type: "http",
name: "Test Monitor",
}),
},
];
const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }];
monitorFindStub.resolves(mockMonitors);
checkFindStub.resolves(mockChecks);
const result = await getAllMonitorsWithUptimeStats();
expect(result).to.be.an("array");
expect(result).to.have.lengthOf(1);
const monitor = result[0];
expect(monitor).to.have.property("_id", "monitor1");
expect(monitor).to.have.property("name", "Test Monitor");
// Check uptime percentages exist for all time periods
expect(monitor).to.have.property("1");
expect(monitor).to.have.property("7");
expect(monitor).to.have.property("30");
expect(monitor).to.have.property("90");
// Verify uptime percentage calculation (3 successful out of 4 = 75%)
expect(monitor["1"]).to.equal(75);
expect(monitor["7"]).to.equal(75);
expect(monitor["30"]).to.equal(75);
expect(monitor["90"]).to.equal(75);
});
it("should return monitors with stats for pagespeed type", async function () {
// Mock data
const mockMonitors = [
{
_id: "monitor1",
type: "pagespeed",
toObject: () => ({
_id: "monitor1",
type: "pagespeed",
name: "Test Monitor",
}),
},
];
const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }];
monitorFindStub.resolves(mockMonitors);
pageSpeedCheckFindStub.resolves(mockChecks);
const result = await getAllMonitorsWithUptimeStats();
expect(result).to.be.an("array");
expect(result).to.have.lengthOf(1);
const monitor = result[0];
expect(monitor).to.have.property("_id", "monitor1");
expect(monitor).to.have.property("name", "Test Monitor");
// Check uptime percentages exist for all time periods
expect(monitor).to.have.property("1");
expect(monitor).to.have.property("7");
expect(monitor).to.have.property("30");
expect(monitor).to.have.property("90");
// Verify uptime percentage calculation (3 successful out of 4 = 75%)
expect(monitor["1"]).to.equal(75);
expect(monitor["7"]).to.equal(75);
expect(monitor["30"]).to.equal(75);
expect(monitor["90"]).to.equal(75);
});
it("should return monitors with stats for hardware type", async function () {
// Mock data
const mockMonitors = [
{
_id: "monitor1",
type: "hardware",
toObject: () => ({
_id: "monitor1",
type: "hardware",
name: "Test Monitor",
}),
},
];
const mockChecks = [{ status: true }, { status: true }, { status: false }, { status: true }];
monitorFindStub.resolves(mockMonitors);
hardwareCheckFindStub.resolves(mockChecks);
const result = await getAllMonitorsWithUptimeStats();
expect(result).to.be.an("array");
expect(result).to.have.lengthOf(1);
const monitor = result[0];
expect(monitor).to.have.property("_id", "monitor1");
expect(monitor).to.have.property("name", "Test Monitor");
// Check uptime percentages exist for all time periods
expect(monitor).to.have.property("1");
expect(monitor).to.have.property("7");
expect(monitor).to.have.property("30");
expect(monitor).to.have.property("90");
// Verify uptime percentage calculation (3 successful out of 4 = 75%)
expect(monitor["1"]).to.equal(75);
expect(monitor["7"]).to.equal(75);
expect(monitor["30"]).to.equal(75);
expect(monitor["90"]).to.equal(75);
});
it("should handle errors appropriately", async function () {
// Setup stub to throw error
monitorFindStub.rejects(new Error("Database error"));
try {
await getAllMonitorsWithUptimeStats();
} catch (error) {
expect(error).to.be.an("error");
expect(error.message).to.equal("Database error");
expect(error.service).to.equal("monitorModule");
expect(error.method).to.equal("getAllMonitorsWithUptimeStats");
}
});
it("should handle empty monitor list", async function () {
monitorFindStub.resolves([]);
const result = await getAllMonitorsWithUptimeStats();
expect(result).to.be.an("array");
expect(result).to.have.lengthOf(0);
});
it("should handle monitor with no checks", async function () {
const mockMonitors = [
{
_id: "monitor1",
type: "http",
toObject: () => ({
_id: "monitor1",
type: "http",
name: "Test Monitor",
}),
},
];
monitorFindStub.resolves(mockMonitors);
checkFindStub.resolves([]);
const result = await getAllMonitorsWithUptimeStats();
expect(result[0]).to.have.property("1", 0);
expect(result[0]).to.have.property("7", 0);
expect(result[0]).to.have.property("30", 0);
expect(result[0]).to.have.property("90", 0);
});
});
describe("calculateUptimeDuration", function () {
let clock;
const NOW = new Date("2024-01-01T12:00:00Z").getTime();
beforeEach(function () {
// Fix the current time
clock = sinon.useFakeTimers(NOW);
});
afterEach(function () {
clock.restore();
});
it("should return 0 when checks array is empty", function () {
expect(calculateUptimeDuration([])).to.equal(0);
});
it("should return 0 when checks array is null", function () {
expect(calculateUptimeDuration(null)).to.equal(0);
});
it("should calculate uptime from last down check to most recent check", function () {
const checks = [
{ status: true, createdAt: "2024-01-01T11:00:00Z" }, // Most recent
{ status: true, createdAt: "2024-01-01T10:00:00Z" },
{ status: false, createdAt: "2024-01-01T09:00:00Z" }, // Last down
{ status: true, createdAt: "2024-01-01T08:00:00Z" },
];
// Expected: 2 hours (from 09:00 to 11:00) = 7200000ms
expect(calculateUptimeDuration(checks)).to.equal(7200000);
});
it("should calculate uptime from first check when no down checks exist", function () {
const checks = [
{ status: true, createdAt: "2024-01-01T11:00:00Z" },
{ status: true, createdAt: "2024-01-01T10:00:00Z" },
{ status: true, createdAt: "2024-01-01T09:00:00Z" },
];
// Expected: Current time (12:00) - First check (09:00) = 3 hours = 10800000ms
expect(calculateUptimeDuration(checks)).to.equal(10800000);
});
});
describe("getLastChecked", function () {
let clock;
const NOW = new Date("2024-01-01T12:00:00Z").getTime();
beforeEach(function () {
// Fix the current time
clock = sinon.useFakeTimers(NOW);
});
afterEach(function () {
clock.restore();
});
it("should return 0 when checks array is empty", function () {
expect(getLastChecked([])).to.equal(0);
});
it("should return 0 when checks array is null", function () {
expect(getLastChecked(null)).to.equal(0);
});
it("should return time difference between now and most recent check", function () {
const checks = [
{ createdAt: "2024-01-01T11:30:00Z" }, // 30 minutes ago
{ createdAt: "2024-01-01T11:00:00Z" },
{ createdAt: "2024-01-01T10:30:00Z" },
];
// Expected: 30 minutes = 1800000ms
expect(getLastChecked(checks)).to.equal(1800000);
});
it("should handle checks from different days", function () {
const checks = [
{ createdAt: "2023-12-31T12:00:00Z" }, // 24 hours ago
{ createdAt: "2023-12-30T12:00:00Z" },
];
// Expected: 24 hours = 86400000ms
expect(getLastChecked(checks)).to.equal(86400000);
});
});
describe("getLatestResponseTime", function () {
it("should return 0 when checks array is empty", function () {
expect(getLatestResponseTime([])).to.equal(0);
});
it("should return 0 when checks array is null", function () {
expect(getLatestResponseTime(null)).to.equal(0);
});
it("should return response time from most recent check", function () {
const checks = [
{ responseTime: 150, createdAt: "2024-01-01T11:30:00Z" }, // Most recent
{ responseTime: 200, createdAt: "2024-01-01T11:00:00Z" },
{ responseTime: 250, createdAt: "2024-01-01T10:30:00Z" },
];
expect(getLatestResponseTime(checks)).to.equal(150);
});
it("should handle missing responseTime in checks", function () {
const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { responseTime: 200, createdAt: "2024-01-01T11:00:00Z" }];
expect(getLatestResponseTime(checks)).to.equal(0);
});
});
describe("getAverageResponseTime", function () {
it("should return 0 when checks array is empty", function () {
expect(getAverageResponseTime([])).to.equal(0);
});
it("should return 0 when checks array is null", function () {
expect(getAverageResponseTime(null)).to.equal(0);
});
it("should calculate average response time from all checks", function () {
const checks = [
{ responseTime: 100, createdAt: "2024-01-01T11:30:00Z" },
{ responseTime: 200, createdAt: "2024-01-01T11:00:00Z" },
{ responseTime: 300, createdAt: "2024-01-01T10:30:00Z" },
];
// Average: (100 + 200 + 300) / 3 = 200
expect(getAverageResponseTime(checks)).to.equal(200);
});
it("should handle missing responseTime in some checks", function () {
const checks = [
{ responseTime: 100, createdAt: "2024-01-01T11:30:00Z" },
{ createdAt: "2024-01-01T11:00:00Z" },
{ responseTime: 300, createdAt: "2024-01-01T10:30:00Z" },
];
// Average: (100 + 300) / 2 = 200
expect(getAverageResponseTime(checks)).to.equal(200);
});
it("should return 0 when no checks have responseTime", function () {
const checks = [{ createdAt: "2024-01-01T11:30:00Z" }, { createdAt: "2024-01-01T11:00:00Z" }];
expect(getAverageResponseTime(checks)).to.equal(0);
});
});
describe("getUptimePercentage", function () {
it("should return 0 when checks array is empty", function () {
expect(getUptimePercentage([])).to.equal(0);
});
it("should return 0 when checks array is null", function () {
expect(getUptimePercentage(null)).to.equal(0);
});
it("should return 100 when all checks are up", function () {
const checks = [{ status: true }, { status: true }, { status: true }];
expect(getUptimePercentage(checks)).to.equal(100);
});
it("should return 0 when all checks are down", function () {
const checks = [{ status: false }, { status: false }, { status: false }];
expect(getUptimePercentage(checks)).to.equal(0);
});
it("should calculate correct percentage for mixed status checks", function () {
const checks = [{ status: true }, { status: false }, { status: true }, { status: true }];
// 3 up out of 4 total = 75%
expect(getUptimePercentage(checks)).to.equal(75);
});
it("should handle undefined status values", function () {
const checks = [{ status: true }, { status: undefined }, { status: true }];
// 2 up out of 3 total ≈ 66.67%
expect(getUptimePercentage(checks)).to.equal((2 / 3) * 100);
});
});
describe("getIncidents", function () {
it("should return 0 when checks array is empty", function () {
expect(getIncidents([])).to.equal(0);
});
it("should return 0 when checks array is null", function () {
expect(getIncidents(null)).to.equal(0);
});
it("should return 0 when all checks are up", function () {
const checks = [{ status: true }, { status: true }, { status: true }];
expect(getIncidents(checks)).to.equal(0);
});
it("should count all incidents when all checks are down", function () {
const checks = [{ status: false }, { status: false }, { status: false }];
expect(getIncidents(checks)).to.equal(3);
});
it("should count correct number of incidents for mixed status checks", function () {
const checks = [{ status: true }, { status: false }, { status: true }, { status: false }, { status: true }];
expect(getIncidents(checks)).to.equal(2);
});
it("should handle undefined status values", function () {
const checks = [{ status: true }, { status: undefined }, { status: false }, { status: false }];
// Only counts explicit false values
expect(getIncidents(checks)).to.equal(2);
});
});
describe("getMonitorChecks", function () {
let mockModel;
beforeEach(function () {
// Create a mock model with chainable methods
const mockChecks = [
{ monitorId: "123", createdAt: new Date("2024-01-01") },
{ monitorId: "123", createdAt: new Date("2024-01-02") },
];
mockModel = {
find: sinon.stub().returns({
sort: sinon.stub().returns(mockChecks),
}),
};
});
afterEach(function () {
sinon.restore();
});
it("should return all checks and date-ranged checks", async function () {
// Arrange
const monitorId = "123";
const dateRange = {
start: new Date("2024-01-01"),
end: new Date("2024-01-02"),
};
const sortOrder = -1;
// Act
const result = await getMonitorChecks(monitorId, mockModel, dateRange, sortOrder);
// Assert
expect(result).to.have.keys(["checksAll", "checksForDateRange"]);
// Verify find was called with correct parameters
expect(mockModel.find.firstCall.args[0]).to.deep.equal({ monitorId });
expect(mockModel.find.secondCall.args[0]).to.deep.equal({
monitorId,
createdAt: { $gte: dateRange.start, $lte: dateRange.end },
});
// Verify sort was called with correct parameters
const sortCalls = mockModel.find().sort.getCalls();
sortCalls.forEach((call) => {
expect(call.args[0]).to.deep.equal({ createdAt: sortOrder });
});
});
it("should handle empty results", async function () {
// Arrange
const emptyModel = {
find: sinon.stub().returns({
sort: sinon.stub().returns([]),
}),
};
// Act
const result = await getMonitorChecks(
"123",
emptyModel,
{
start: new Date(),
end: new Date(),
},
-1
);
// Assert
expect(result.checksAll).to.be.an("array").that.is.empty;
expect(result.checksForDateRange).to.be.an("array").that.is.empty;
});
it("should maintain sort order", async function () {
// Arrange
const sortedChecks = [
{ monitorId: "123", createdAt: new Date("2024-01-02") },
{ monitorId: "123", createdAt: new Date("2024-01-01") },
];
const sortedModel = {
find: sinon.stub().returns({
sort: sinon.stub().returns(sortedChecks),
}),
};
// Act
const result = await getMonitorChecks(
"123",
sortedModel,
{
start: new Date("2024-01-01"),
end: new Date("2024-01-02"),
},
-1
);
// Assert
expect(result.checksAll[0].createdAt).to.be.greaterThan(result.checksAll[1].createdAt);
expect(result.checksForDateRange[0].createdAt).to.be.greaterThan(result.checksForDateRange[1].createdAt);
});
});
describe("processChecksForDisplay", function () {
let normalizeStub;
beforeEach(function () {
normalizeStub = sinon.stub();
});
it("should return original checks when numToDisplay is not provided", function () {
const checks = [1, 2, 3, 4, 5];
const result = processChecksForDisplay(normalizeStub, checks);
expect(result).to.deep.equal(checks);
});
it("should return original checks when numToDisplay is greater than checks length", function () {
const checks = [1, 2, 3];
const result = processChecksForDisplay(normalizeStub, checks, 5);
expect(result).to.deep.equal(checks);
});
it("should filter checks based on numToDisplay", function () {
const checks = [1, 2, 3, 4, 5, 6];
const result = processChecksForDisplay(normalizeStub, checks, 3);
// Should return [1, 3, 5] as n = ceil(6/3) = 2
expect(result).to.deep.equal([1, 3, 5]);
});
it("should handle empty checks array", function () {
const checks = [];
const result = processChecksForDisplay(normalizeStub, checks, 3);
expect(result).to.be.an("array").that.is.empty;
});
it("should call normalizeData when normalize is true", function () {
const checks = [1, 2, 3];
normalizeStub.returns([10, 20, 30]);
const result = processChecksForDisplay(normalizeStub, checks, null, true);
expect(normalizeStub.args[0]).to.deep.equal([checks, 1, 100]);
expect(result).to.deep.equal([10, 20, 30]);
});
it("should handle both filtering and normalization", function () {
const checks = [1, 2, 3, 4, 5, 6];
normalizeStub.returns([10, 30, 50]);
const result = processChecksForDisplay(normalizeStub, checks, 3, true);
expect(normalizeStub.args[0][0]).to.deep.equal([1, 3, 5]);
expect(result).to.deep.equal([10, 30, 50]);
});
});
describe("groupChecksByTime", function () {
const mockChecks = [
{ createdAt: "2024-01-15T10:30:45Z" },
{ createdAt: "2024-01-15T10:45:15Z" },
{ createdAt: "2024-01-15T11:15:00Z" },
{ createdAt: "2024-01-16T10:30:00Z" },
];
it("should group checks by hour when dateRange is 'day'", function () {
const result = groupChecksByTime(mockChecks, "day");
// Get timestamps for 10:00 and 11:00 on Jan 15
const time1 = new Date("2024-01-15T10:00:00Z").getTime();
const time2 = new Date("2024-01-15T11:00:00Z").getTime();
const time3 = new Date("2024-01-16T10:00:00Z").getTime();
expect(Object.keys(result)).to.have.lengthOf(3);
expect(result[time1].checks).to.have.lengthOf(2);
expect(result[time2].checks).to.have.lengthOf(1);
expect(result[time3].checks).to.have.lengthOf(1);
});
it("should group checks by day when dateRange is not 'day'", function () {
const result = groupChecksByTime(mockChecks, "week");
expect(Object.keys(result)).to.have.lengthOf(2);
expect(result["2024-01-15"].checks).to.have.lengthOf(3);
expect(result["2024-01-16"].checks).to.have.lengthOf(1);
});
it("should handle empty checks array", function () {
const result = groupChecksByTime([], "day");
expect(result).to.deep.equal({});
});
it("should handle single check", function () {
const singleCheck = [{ createdAt: "2024-01-15T10:30:45Z" }];
const result = groupChecksByTime(singleCheck, "day");
const expectedTime = new Date("2024-01-15T10:00:00Z").getTime();
expect(Object.keys(result)).to.have.lengthOf(1);
expect(result[expectedTime].checks).to.have.lengthOf(1);
});
it("should skip invalid dates and process valid ones", function () {
const checksWithInvalidDate = [
{ createdAt: "invalid-date" },
{ createdAt: "2024-01-15T10:30:45Z" },
{ createdAt: null },
{ createdAt: undefined },
{ createdAt: "" },
];
const result = groupChecksByTime(checksWithInvalidDate, "day");
const expectedTime = new Date("2024-01-15T10:00:00Z").getTime();
expect(Object.keys(result)).to.have.lengthOf(1);
expect(result[expectedTime].checks).to.have.lengthOf(1);
expect(result[expectedTime].checks[0].createdAt).to.equal("2024-01-15T10:30:45Z");
});
it("should handle checks in same time group", function () {
const checksInSameHour = [{ createdAt: "2024-01-15T10:15:00Z" }, { createdAt: "2024-01-15T10:45:00Z" }];
const result = groupChecksByTime(checksInSameHour, "day");
const expectedTime = new Date("2024-01-15T10:00:00Z").getTime();
expect(Object.keys(result)).to.have.lengthOf(1);
expect(result[expectedTime].checks).to.have.lengthOf(2);
});
});
describe("calculateGroupStats", function () {
// Mock getUptimePercentage function
let uptimePercentageStub;
beforeEach(function () {
uptimePercentageStub = sinon.stub();
uptimePercentageStub.returns(95); // Default return value
});
it("should calculate stats correctly for a group of checks", function () {
const mockGroup = {
time: "2024-01-15",
checks: [
{ status: true, responseTime: 100 },
{ status: false, responseTime: 200 },
{ status: true, responseTime: 300 },
],
};
const result = calculateGroupStats(mockGroup, uptimePercentageStub);
expect(result).to.deep.equal({
time: "2024-01-15",
uptimePercentage: (2 / 3) * 100,
totalChecks: 3,
totalIncidents: 1,
avgResponseTime: 200, // (100 + 200 + 300) / 3
});
});
it("should handle empty checks array", function () {
const mockGroup = {
time: "2024-01-15",
checks: [],
};
const result = calculateGroupStats(mockGroup, uptimePercentageStub);
expect(result).to.deep.equal({
time: "2024-01-15",
uptimePercentage: 0,
totalChecks: 0,
totalIncidents: 0,
avgResponseTime: 0,
});
});
it("should handle missing responseTime values", function () {
const mockGroup = {
time: "2024-01-15",
checks: [{ status: true }, { status: false, responseTime: 200 }, { status: true, responseTime: undefined }],
};
const result = calculateGroupStats(mockGroup, uptimePercentageStub);
expect(result).to.deep.equal({
time: "2024-01-15",
uptimePercentage: (2 / 3) * 100,
totalChecks: 3,
totalIncidents: 1,
avgResponseTime: 200, // 200 / 1
});
});
it("should handle all checks with status false", function () {
const mockGroup = {
time: "2024-01-15",
checks: [
{ status: false, responseTime: 100 },
{ status: false, responseTime: 200 },
{ status: false, responseTime: 300 },
],
};
const result = calculateGroupStats(mockGroup, uptimePercentageStub);
expect(result).to.deep.equal({
time: "2024-01-15",
uptimePercentage: 0,
totalChecks: 3,
totalIncidents: 3,
avgResponseTime: 200,
});
});
it("should handle all checks with status true", function () {
const mockGroup = {
time: "2024-01-15",
checks: [
{ status: true, responseTime: 100 },
{ status: true, responseTime: 200 },
{ status: true, responseTime: 300 },
],
};
const result = calculateGroupStats(mockGroup, uptimePercentageStub);
expect(result).to.deep.equal({
time: "2024-01-15",
uptimePercentage: 100,
totalChecks: 3,
totalIncidents: 0,
avgResponseTime: 200,
});
});
});
describe("getMonitorStatsById", function () {
const now = new Date();
const oneHourAgo = new Date(now - 3600000);
const twoHoursAgo = new Date(now - 7200000);
const mockMonitor = {
_id: "monitor123",
type: "http",
name: "Test Monitor",
url: "https://test.com",
toObject: () => ({
_id: "monitor123",
type: "http",
name: "Test Monitor",
url: "https://test.com",
}),
};
const mockMonitorPing = {
_id: "monitor123",
type: "ping",
name: "Test Monitor",
url: "https://test.com",
toObject: () => ({
_id: "monitor123",
type: "http",
name: "Test Monitor",
url: "https://test.com",
}),
};
const mockMonitorDocker = {
_id: "monitor123",
type: "docker",
name: "Test Monitor",
url: "https://test.com",
toObject: () => ({
_id: "monitor123",
type: "http",
name: "Test Monitor",
url: "https://test.com",
}),
};
const checkDocs = [
{
monitorId: "monitor123",
status: true,
responseTime: 100,
createdAt: new Date("2024-01-01T12:00:00Z"),
toObject: function () {
return {
monitorId: this.monitorId,
status: this.status,
responseTime: this.responseTime,
createdAt: this.createdAt,
};
},
},
{
monitorId: "monitor123",
status: true,
responseTime: 150,
createdAt: new Date("2024-01-01T11:00:00Z"),
toObject: function () {
return {
monitorId: this.monitorId,
status: this.status,
responseTime: this.responseTime,
createdAt: this.createdAt,
};
},
},
{
monitorId: "monitor123",
status: false,
responseTime: 200,
createdAt: new Date("2024-01-01T10:00:00Z"),
toObject: function () {
return {
monitorId: this.monitorId,
status: this.status,
responseTime: this.responseTime,
createdAt: this.createdAt,
};
},
},
];
const req = {
params: { monitorId: "monitor123" },
query: {
dateRange: "day",
sortOrder: "desc",
numToDisplay: 10,
normalize: true,
},
};
beforeEach(function () {
checkFindStub.returns({
sort: () => checkDocs,
});
monitorFindByIdStub.returns(mockMonitor);
});
afterEach(function () {
sinon.restore();
});
it("should return monitor stats with calculated values, sort order desc", async function () {
req.query.sortOrder = "desc";
const result = await getMonitorStatsById(req);
expect(result).to.include.keys([
"_id",
"type",
"name",
"url",
"uptimeDuration",
"lastChecked",
"latestResponseTime",
"periodIncidents",
"periodTotalChecks",
"periodAvgResponseTime",
"periodUptime",
"aggregateData",
]);
expect(result.latestResponseTime).to.equal(100);
expect(result.periodTotalChecks).to.equal(3);
expect(result.periodIncidents).to.equal(1);
expect(result.periodUptime).to.be.a("number");
expect(result.aggregateData).to.be.an("array");
});
it("should return monitor stats with calculated values, ping type", async function () {
monitorFindByIdStub.returns(mockMonitorPing);
req.query.sortOrder = "desc";
const result = await getMonitorStatsById(req);
expect(result).to.include.keys([
"_id",
"type",
"name",
"url",
"uptimeDuration",
"lastChecked",
"latestResponseTime",
"periodIncidents",
"periodTotalChecks",
"periodAvgResponseTime",
"periodUptime",
"aggregateData",
]);
expect(result.latestResponseTime).to.equal(100);
expect(result.periodTotalChecks).to.equal(3);
expect(result.periodIncidents).to.equal(1);
expect(result.periodUptime).to.be.a("number");
expect(result.aggregateData).to.be.an("array");
});
it("should return monitor stats with calculated values, docker type", async function () {
monitorFindByIdStub.returns(mockMonitorDocker);
req.query.sortOrder = "desc";
const result = await getMonitorStatsById(req);
expect(result).to.include.keys([
"_id",
"type",
"name",
"url",
"uptimeDuration",
"lastChecked",
"latestResponseTime",
"periodIncidents",
"periodTotalChecks",
"periodAvgResponseTime",
"periodUptime",
"aggregateData",
]);
expect(result.latestResponseTime).to.equal(100);
expect(result.periodTotalChecks).to.equal(3);
expect(result.periodIncidents).to.equal(1);
expect(result.periodUptime).to.be.a("number");
expect(result.aggregateData).to.be.an("array");
});
it("should return monitor stats with calculated values", async function () {
req.query.sortOrder = "asc";
const result = await getMonitorStatsById(req);
expect(result).to.include.keys([
"_id",
"type",
"name",
"url",
"uptimeDuration",
"lastChecked",
"latestResponseTime",
"periodIncidents",
"periodTotalChecks",
"periodAvgResponseTime",
"periodUptime",
"aggregateData",
]);
expect(result.latestResponseTime).to.equal(100);
expect(result.periodTotalChecks).to.equal(3);
expect(result.periodIncidents).to.equal(1);
expect(result.periodUptime).to.be.a("number");
expect(result.aggregateData).to.be.an("array");
});
it("should throw error when monitor is not found", async function () {
monitorFindByIdStub.returns(Promise.resolve(null));
const req = {
params: { monitorId: "nonexistent" },
};
try {
await getMonitorStatsById(req);
expect.fail("Should have thrown an error");
} catch (error) {
expect(error).to.be.an("Error");
expect(error.service).to.equal("monitorModule");
expect(error.method).to.equal("getMonitorStatsById");
}
});
});
describe("getMonitorById", function () {
let notificationFindStub;
let monitorSaveStub;
beforeEach(function () {
// Create stubs
notificationFindStub = sinon.stub(Notification, "find");
monitorSaveStub = sinon.stub().resolves();
});
afterEach(function () {
sinon.restore();
});
it("should return monitor with notifications when found", async function () {
// Arrange
const monitorId = "123";
const mockMonitor = {
_id: monitorId,
name: "Test Monitor",
save: monitorSaveStub,
};
const mockNotifications = [
{ _id: "notif1", message: "Test notification 1" },
{ _id: "notif2", message: "Test notification 2" },
];
monitorFindByIdStub.resolves(mockMonitor);
notificationFindStub.resolves(mockNotifications);
const result = await getMonitorById(monitorId);
expect(result._id).to.equal(monitorId);
expect(result.name).to.equal("Test Monitor");
expect(monitorFindByIdStub.calledWith(monitorId)).to.be.true;
expect(notificationFindStub.calledWith({ monitorId })).to.be.true;
expect(monitorSaveStub.calledOnce).to.be.true;
});
it("should throw 404 error when monitor not found", async function () {
// Arrange
const monitorId = "nonexistent";
monitorFindByIdStub.resolves(null);
// Act & Assert
try {
await getMonitorById(monitorId);
expect.fail("Should have thrown an error");
} catch (error) {
expect(error.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
expect(error.status).to.equal(404);
expect(error.service).to.equal("monitorModule");
expect(error.method).to.equal("getMonitorById");
}
});
it("should handle database errors properly", async function () {
// Arrange
const monitorId = "123";
const dbError = new Error("Database connection failed");
monitorFindByIdStub.rejects(dbError);
// Act & Assert
try {
await getMonitorById(monitorId);
expect.fail("Should have thrown an error");
} catch (error) {
expect(error.service).to.equal("monitorModule");
expect(error.method).to.equal("getMonitorById");
expect(error.message).to.equal("Database connection failed");
}
});
it("should handle notification fetch errors", async function () {
// Arrange
const monitorId = "123";
const mockMonitor = {
_id: monitorId,
name: "Test Monitor",
save: monitorSaveStub,
};
const notificationError = new Error("Notification fetch failed");
monitorFindByIdStub.resolves(mockMonitor);
notificationFindStub.rejects(notificationError);
// Act & Assert
try {
await getMonitorById(monitorId);
expect.fail("Should have thrown an error");
} catch (error) {
expect(error.service).to.equal("monitorModule");
expect(error.method).to.equal("getMonitorById");
expect(error.message).to.equal("Notification fetch failed");
}
});
it("should handle monitor save errors", async function () {
// Arrange
const monitorId = "123";
const mockMonitor = {
_id: monitorId,
name: "Test Monitor",
save: sinon.stub().rejects(new Error("Save failed")),
};
const mockNotifications = [];
monitorFindByIdStub.resolves(mockMonitor);
notificationFindStub.resolves(mockNotifications);
// Act & Assert
try {
await getMonitorById(monitorId);
expect.fail("Should have thrown an error");
} catch (error) {
expect(error.service).to.equal("monitorModule");
expect(error.method).to.equal("getMonitorById");
expect(error.message).to.equal("Save failed");
}
});
});
describe("getMonitorsAndSummaryByTeamId", function () {
it("should return monitors and correct summary counts", async function () {
// Arrange
const teamId = "team123";
const type = "http";
const mockMonitors = [
{ teamId, type, status: true, isActive: true }, // up
{ teamId, type, status: false, isActive: true }, // down
{ teamId, type, status: null, isActive: false }, // paused
{ teamId, type, status: true, isActive: true }, // up
];
monitorFindStub.resolves(mockMonitors);
// Act
const result = await getMonitorsAndSummaryByTeamId(teamId, type);
// Assert
expect(result.monitors).to.have.lengthOf(4);
expect(result.monitorCounts).to.deep.equal({
up: 2,
down: 1,
paused: 1,
total: 4,
});
expect(monitorFindStub.calledOnceWith({ teamId, type })).to.be.true;
});
it("should return empty results for non-existent team", async function () {
// Arrange
monitorFindStub.resolves([]);
// Act
const result = await getMonitorsAndSummaryByTeamId("nonexistent", "http");
// Assert
expect(result.monitors).to.have.lengthOf(0);
expect(result.monitorCounts).to.deep.equal({
up: 0,
down: 0,
paused: 0,
total: 0,
});
});
it("should handle database errors", async function () {
// Arrange
const error = new Error("Database error");
error.service = "MonitorModule";
error.method = "getMonitorsAndSummaryByTeamId";
monitorFindStub.rejects(error);
// Act & Assert
try {
await getMonitorsAndSummaryByTeamId("team123", "http");
expect.fail("Should have thrown an error");
} catch (err) {
expect(err).to.equal(error);
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("getMonitorsAndSummaryByTeamId");
}
});
});
describe("getMonitorsByTeamId", function () {
beforeEach(function () {
// Chain stubs for Monitor.find().skip().limit().sort()
// Stub for CHECK_MODEL_LOOKUP model find
checkFindStub.returns({
sort: sinon.stub().returns({
limit: sinon.stub().returns([]),
}),
});
});
afterEach(function () {
sinon.restore();
});
it("should return monitors with basic query parameters", async function () {
const mockMonitors = [
{ _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) },
{ _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) },
];
monitorFindStub.returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
sort: sinon.stub().returns(mockMonitors),
}),
}),
});
const req = {
params: { teamId: "team123" },
query: {
type: "http",
page: 0,
rowsPerPage: 10,
field: "name",
status: false,
checkOrder: "desc",
},
};
monitorCountStub.resolves(2);
const result = await getMonitorsByTeamId(req);
expect(result).to.have.property("monitors");
expect(result).to.have.property("monitorCount", 2);
});
it("should return monitors with basic query parameters", async function () {
const mockMonitors = [
{ _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) },
{ _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) },
];
monitorFindStub.returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
sort: sinon.stub().returns(mockMonitors),
}),
}),
});
const req = {
params: { teamId: "team123" },
query: {
type: "http",
page: 0,
rowsPerPage: 10,
field: "name",
status: true,
checkOrder: "asc",
},
};
monitorCountStub.resolves(2);
const result = await getMonitorsByTeamId(req);
expect(result).to.have.property("monitors");
expect(result).to.have.property("monitorCount", 2);
});
it("should handle type filter with array input", async function () {
const req = {
params: { teamId: "team123" },
query: {
type: ["http", "ping"],
},
};
monitorFindStub.returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
sort: sinon.stub().returns([]),
}),
}),
});
monitorCountStub.resolves(0);
await getMonitorsByTeamId(req);
expect(Monitor.find.firstCall.args[0]).to.deep.equal({
teamId: "team123",
type: { $in: ["http", "ping"] },
});
});
it("should handle text search filter", async function () {
const req = {
params: { teamId: "team123" },
query: {
filter: "search",
},
};
monitorFindStub.returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
sort: sinon.stub().returns([]),
}),
}),
});
monitorCountStub.resolves(0);
await getMonitorsByTeamId(req);
expect(Monitor.find.firstCall.args[0]).to.deep.equal({
teamId: "team123",
$or: [{ name: { $regex: "search", $options: "i" } }, { url: { $regex: "search", $options: "i" } }],
});
});
it("should handle pagination parameters", async function () {
const req = {
params: { teamId: "team123" },
query: {
page: 2,
rowsPerPage: 5,
},
};
monitorFindStub.returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
sort: sinon.stub().returns([]),
}),
}),
});
monitorCountStub.resolves(0);
const result = await getMonitorsByTeamId(req);
expect(result).to.deep.equal({
monitors: [],
monitorCount: 0,
});
});
it("should handle sorting parameters", async function () {
const req = {
params: { teamId: "team123" },
query: {
field: "name",
order: "asc",
},
};
monitorFindStub.returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
sort: sinon.stub().returns([]),
}),
}),
});
monitorCountStub.resolves(0);
await getMonitorsByTeamId(req);
const result = await getMonitorsByTeamId(req);
expect(result).to.deep.equal({
monitors: [],
monitorCount: 0,
});
});
it("should return early when limit is -1", async function () {
// Arrange
const req = {
params: { teamId: "team123" },
query: {
limit: "-1",
},
};
const mockMonitors = [
{ _id: "1", type: "http" },
{ _id: "2", type: "ping" },
];
monitorFindStub.returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
sort: sinon.stub().returns(mockMonitors),
}),
}),
});
monitorCountStub.resolves(2);
// Act
const result = await getMonitorsByTeamId(req);
// Assert
expect(result).to.deep.equal({
monitors: mockMonitors,
monitorCount: 2,
});
});
it("should normalize checks when normalize parameter is provided", async function () {
const req = {
params: { teamId: "team123" },
query: { normalize: "true" },
};
monitorCountStub.resolves(2);
const mockMonitors = [
{ _id: "1", type: "http", toObject: () => ({ _id: "1", type: "http" }) },
{ _id: "2", type: "ping", toObject: () => ({ _id: "2", type: "ping" }) },
];
monitorFindStub.returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
sort: sinon.stub().returns(mockMonitors),
}),
}),
});
const result = await getMonitorsByTeamId(req);
expect(result.monitorCount).to.equal(2);
expect(result.monitors).to.have.lengthOf(2);
});
it("should handle database errors", async function () {
const req = {
params: { teamId: "team123" },
query: {},
};
const error = new Error("Database error");
monitorFindStub.returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
sort: sinon.stub().throws(error),
}),
}),
});
try {
await getMonitorsByTeamId(req);
expect.fail("Should have thrown an error");
} catch (err) {
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("getMonitorsByTeamId");
expect(err.message).to.equal("Database error");
}
});
});
describe("createMonitor", function () {
it("should create a monitor without notifications", async function () {
let monitorSaveStub = sinon.stub(Monitor.prototype, "save").resolves();
const req = {
body: {
name: "Test Monitor",
url: "http://test.com",
type: "http",
notifications: ["someNotification"],
},
};
const expectedMonitor = {
name: "Test Monitor",
url: "http://test.com",
type: "http",
notifications: undefined,
save: monitorSaveStub,
};
const result = await createMonitor(req);
expect(result.name).to.equal(expectedMonitor.name);
expect(result.url).to.equal(expectedMonitor.url);
});
it("should handle database errors", async function () {
const req = {
body: {
name: "Test Monitor",
},
};
try {
await createMonitor(req);
expect.fail("Should have thrown an error");
} catch (err) {
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("createMonitor");
}
});
});
describe("deleteMonitor", function () {
it("should delete a monitor successfully", async function () {
const monitorId = "123456789";
const mockMonitor = {
_id: monitorId,
name: "Test Monitor",
url: "http://test.com",
};
const req = {
params: { monitorId },
};
monitorFindByIdAndDeleteStub.resolves(mockMonitor);
const result = await deleteMonitor(req);
expect(result).to.deep.equal(mockMonitor);
sinon.assert.calledWith(monitorFindByIdAndDeleteStub, monitorId);
});
it("should throw error when monitor not found", async function () {
const monitorId = "nonexistent123";
const req = {
params: { monitorId },
};
monitorFindByIdAndDeleteStub.resolves(null);
try {
await deleteMonitor(req);
expect.fail("Should have thrown an error");
} catch (err) {
expect(err.message).to.equal(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("deleteMonitor");
}
});
it("should handle database errors", async function () {
const monitorId = "123456789";
const req = {
params: { monitorId },
};
const dbError = new Error("Database connection error");
monitorFindByIdAndDeleteStub.rejects(dbError);
try {
await deleteMonitor(req);
expect.fail("Should have thrown an error");
} catch (err) {
expect(err.message).to.equal("Database connection error");
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("deleteMonitor");
}
});
});
describe("deleteAllMonitors", function () {
it("should delete all monitors for a team successfully", async function () {
const teamId = "team123";
const mockMonitors = [
{ _id: "1", name: "Monitor 1", teamId },
{ _id: "2", name: "Monitor 2", teamId },
];
monitorFindStub.resolves(mockMonitors);
monitorDeleteManyStub.resolves({ deletedCount: 2 });
const result = await deleteAllMonitors(teamId);
expect(result).to.deep.equal({
monitors: mockMonitors,
deletedCount: 2,
});
sinon.assert.calledWith(monitorFindStub, { teamId });
sinon.assert.calledWith(monitorDeleteManyStub, { teamId });
});
it("should return empty array when no monitors found", async function () {
const teamId = "emptyTeam";
monitorFindStub.resolves([]);
monitorDeleteManyStub.resolves({ deletedCount: 0 });
const result = await deleteAllMonitors(teamId);
expect(result).to.deep.equal({
monitors: [],
deletedCount: 0,
});
sinon.assert.calledWith(monitorFindStub, { teamId });
sinon.assert.calledWith(monitorDeleteManyStub, { teamId });
});
it("should handle database errors", async function () {
const teamId = "team123";
const dbError = new Error("Database connection error");
monitorFindStub.rejects(dbError);
try {
await deleteAllMonitors(teamId);
} catch (err) {
expect(err.message).to.equal("Database connection error");
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("deleteAllMonitors");
}
});
it("should handle deleteMany errors", async function () {
const teamId = "team123";
monitorFindStub.resolves([{ _id: "1", name: "Monitor 1" }]);
monitorDeleteManyStub.rejects(new Error("Delete operation failed"));
try {
await deleteAllMonitors(teamId);
} catch (err) {
expect(err.message).to.equal("Delete operation failed");
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("deleteAllMonitors");
}
});
});
describe("deleteMonitorsByUserId", function () {
beforeEach(function () {});
afterEach(function () {
sinon.restore();
});
it("should delete all monitors for a user successfully", async function () {
// Arrange
const userId = "user123";
const mockResult = {
deletedCount: 3,
acknowledged: true,
};
monitorDeleteManyStub.resolves(mockResult);
// Act
const result = await deleteMonitorsByUserId(userId);
// Assert
expect(result).to.deep.equal(mockResult);
sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId });
});
it("should return zero deletedCount when no monitors found", async function () {
// Arrange
const userId = "nonexistentUser";
const mockResult = {
deletedCount: 0,
acknowledged: true,
};
monitorDeleteManyStub.resolves(mockResult);
// Act
const result = await deleteMonitorsByUserId(userId);
// Assert
expect(result.deletedCount).to.equal(0);
sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId });
});
it("should handle database errors", async function () {
// Arrange
const userId = "user123";
const dbError = new Error("Database connection error");
monitorDeleteManyStub.rejects(dbError);
// Act & Assert
try {
await deleteMonitorsByUserId(userId);
expect.fail("Should have thrown an error");
} catch (err) {
expect(err.message).to.equal("Database connection error");
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("deleteMonitorsByUserId");
}
});
});
describe("editMonitor", function () {
it("should edit a monitor successfully", async function () {
// Arrange
const candidateId = "monitor123";
const candidateMonitor = {
name: "Updated Monitor",
url: "http://updated.com",
type: "http",
notifications: ["someNotification"],
};
const expectedUpdateData = {
name: "Updated Monitor",
url: "http://updated.com",
type: "http",
notifications: undefined,
};
const mockUpdatedMonitor = {
_id: candidateId,
...expectedUpdateData,
};
monitorFindByIdAndUpdateStub.resolves(mockUpdatedMonitor);
// Act
const result = await editMonitor(candidateId, candidateMonitor);
// Assert
expect(result).to.deep.equal(mockUpdatedMonitor);
sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, {
new: true,
});
});
it("should return null when monitor not found", async function () {
// Arrange
const candidateId = "nonexistent123";
const candidateMonitor = {
name: "Updated Monitor",
};
monitorFindByIdAndUpdateStub.resolves(null);
// Act
const result = await editMonitor(candidateId, candidateMonitor);
// Assert
expect(result).to.be.null;
sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, { name: "Updated Monitor", notifications: undefined }, { new: true });
});
it("should remove notifications from update data", async function () {
// Arrange
const candidateId = "monitor123";
const candidateMonitor = {
name: "Updated Monitor",
notifications: ["notification1", "notification2"],
};
const expectedUpdateData = {
name: "Updated Monitor",
notifications: undefined,
};
monitorFindByIdAndUpdateStub.resolves({
_id: candidateId,
...expectedUpdateData,
});
// Act
await editMonitor(candidateId, candidateMonitor);
// Assert
sinon.assert.calledWith(monitorFindByIdAndUpdateStub, candidateId, expectedUpdateData, {
new: true,
});
});
it("should handle database errors", async function () {
// Arrange
const candidateId = "monitor123";
const candidateMonitor = {
name: "Updated Monitor",
};
const dbError = new Error("Database connection error");
monitorFindByIdAndUpdateStub.rejects(dbError);
// Act & Assert
try {
await editMonitor(candidateId, candidateMonitor);
expect.fail("Should have thrown an error");
} catch (err) {
expect(err.message).to.equal("Database connection error");
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("editMonitor");
}
});
});
describe("addDemoMonitors", function () {
it("should add demo monitors successfully", async function () {
// Arrange
const userId = "user123";
const teamId = "team123";
monitorInsertManyStub.resolves([{ _id: "123" }]);
const result = await addDemoMonitors(userId, teamId);
expect(result).to.deep.equal([{ _id: "123" }]);
});
it("should handle database errors", async function () {
const userId = "user123";
const teamId = "team123";
const dbError = new Error("Database connection error");
monitorInsertManyStub.rejects(dbError);
try {
await addDemoMonitors(userId, teamId);
} catch (err) {
expect(err.message).to.equal("Database connection error");
expect(err.service).to.equal("monitorModule");
expect(err.method).to.equal("addDemoMonitors");
}
});
});
});