Merge branch 'develop' into feat/fe/statuspage-3

This commit is contained in:
Shemy Gan committed 2025-01-03 14:28:37 -05:00
commit a544a0f7e5
69 files changed
+3559 -1592

No files matched your search

@@ -1,4 +1,4 @@
**(when sending your PR, only remove this line. Do not remove any lines below. Check all the items before sending your PR)**
**(when sending your PR, only remove this line. Do not remove any lines below. Check all the relevant items before sending your PR)**
## Describe your changes
@@ -17,9 +17,11 @@ import "./index.css";
const CustomToolTip = ({ active, payload, label }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
if (active && payload && payload.length) {
const responseTime = payload[0]?.payload?.originalAvgResponseTime
? payload[0]?.payload?.originalAvgResponseTime
: (payload[0]?.payload?.avgResponseTime ?? 0);
return (
<Box
className="area-tooltip"
@@ -69,7 +71,7 @@ const CustomToolTip = ({ active, payload, label }) => {
Response Time
</Typography>{" "}
<Typography component="span">
{payload[0].payload.originalResponseTime}
{Math.floor(responseTime)}
<Typography
component="span"
sx={{ opacity: 0.8 }}
@@ -87,11 +89,24 @@ const CustomToolTip = ({ active, payload, label }) => {
return null;
};
CustomToolTip.propTypes = {
active: PropTypes.bool,
payload: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.number,
payload: PropTypes.shape({
_id: PropTypes.string,
avgResponseTime: PropTypes.number,
originalAvgResponseTime: PropTypes.number,
}),
})
),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
const CustomTick = ({ x, y, payload, index }) => {
const theme = useTheme();
const uiTimezone = useSelector((state) => state.ui.timezone);
// Render nothing for the first tick
if (index === 0) return null;
return (
@@ -168,7 +183,7 @@ const MonitorDetailsAreaChart = ({ checks }) => {
</defs>
<XAxis
stroke={theme.palette.border.dark}
dataKey="createdAt"
dataKey="_id"
tick={<CustomTick />}
minTickGap={0}
axisLine={false}
@@ -183,7 +198,7 @@ const MonitorDetailsAreaChart = ({ checks }) => {
/>
<Area
type="monotone"
dataKey="responseTime"
dataKey="avgResponseTime"
stroke={theme.palette.primary.main}
fill="url(#colorUv)"
strokeWidth={isHovered ? 2.5 : 1.5}
@@ -198,15 +213,4 @@ MonitorDetailsAreaChart.propTypes = {
checks: PropTypes.array,
};
CustomToolTip.propTypes = {
active: PropTypes.bool,
payload: PropTypes.arrayOf(
PropTypes.shape({
payload: PropTypes.shape({
originalResponseTime: PropTypes.number.isRequired,
}).isRequired,
})
),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
export default MonitorDetailsAreaChart;
@@ -17,7 +17,6 @@ import { Box, Stack, Typography } from "@mui/material";
*/
export const TzTick = ({ x, y, payload, index }) => {
const theme = useTheme();
const uiTimezone = useSelector((state) => state.ui.timezone);
return (
<Text
@@ -163,7 +162,7 @@ export const InfrastructureTooltip = ({
>
{yIdx >= 0
? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}`
: `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][metric])}`}
: `${yLabel} ${getFormattedPercentage(payload[0].payload[yKey])}`}
</Typography>
</Stack>
</Box>
+5 -2
View File
@@ -574,9 +574,12 @@ function Sidebar() {
ml: "auto",
mr: "-8px",
"&:focus": { outline: "none" },
alignSelf: "center",
padding: '10px',
"& svg": {
width: "20px",
height: "20px",
width: "22px",
height: "22px",
},
"& svg path": {
stroke: theme.palette.other.icon,
+103 -59
View File
@@ -1,7 +1,7 @@
import { useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import { Stack, Box, Typography } from "@mui/material";
import { Button, ButtonGroup, Stack, Box, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import CustomGauge from "../../../Components/Charts/CustomGauge";
import AreaChart from "../../../Components/Charts/AreaChart";
@@ -185,9 +185,11 @@ GaugeBox.propTypes = {
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
heading: PropTypes.string.isRequired,
metricOne: PropTypes.string.isRequired,
valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element])
.isRequired,
metricTwo: PropTypes.string.isRequired,
valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element])
.isRequired,
};
/**
@@ -204,7 +206,7 @@ const InfrastructureDetails = () => {
];
const [monitor, setMonitor] = useState(null);
const { authToken } = useSelector((state) => state.auth);
const [dateRange, setDateRange] = useState("all");
const [dateRange, setDateRange] = useState("day");
const { statusColor, statusStyles, determineState } = useUtils();
// These calculations are needed because ResponsiveContainer
// doesn't take padding of parent/siblings into account
@@ -217,8 +219,9 @@ const InfrastructureDetails = () => {
(chartContainerHeight - totalChartContainerPadding - totalTypographyPadding) * 0.95;
// end height calculations
const buildStatBoxes = (checks, uptime) => {
let latestCheck = checks[0] ?? null;
const buildStatBoxes = (stats, uptime) => {
if (Object.keys(stats).length === 0) return [];
let latestCheck = stats?.aggregateData?.latestCheck ?? null;
if (latestCheck === null) return [];
// Extract values from latest check
@@ -311,8 +314,10 @@ const InfrastructureDetails = () => {
];
};
const buildGaugeBoxConfigs = (checks) => {
let latestCheck = checks[0] ?? null;
const buildGaugeBoxConfigs = (stats) => {
if (Object.keys(stats).length === 0) return [];
let latestCheck = stats?.aggregateData?.latestCheck ?? null;
if (latestCheck === null) return [];
// Extract values from latest check
@@ -359,27 +364,27 @@ const InfrastructureDetails = () => {
if (checks === null) return { temps: [], tempKeys: [] };
for (const check of checks) {
if (check?.cpu?.temperature?.length > numCores) {
numCores = check.cpu.temperature.length;
if (check?.avgTemperature?.length > numCores) {
numCores = check.avgTemperature.length;
break;
}
}
const temps = checks.map((check) => {
// If there's no data, set the temperature to 0
if (
check?.cpu?.temperature?.length === 0 ||
check?.cpu?.temperature === undefined ||
check?.cpu?.temperature === null
check?.avgTemperature?.length === 0 ||
check?.avgTemperature === undefined ||
check?.avgTemperature === null
) {
check.cpu.temperature = Array(numCores).fill(0);
check.avgTemperature = Array(numCores).fill(0);
}
const res = check?.cpu?.temperature?.reduce(
const res = check?.avgTemperature?.reduce(
(acc, cur, idx) => {
acc[`core${idx + 1}`] = cur;
return acc;
},
{
createdAt: check.createdAt,
_id: check._id,
}
);
return res;
@@ -389,7 +394,7 @@ const InfrastructureDetails = () => {
}
return {
tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "createdAt"),
tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "_id"),
temps,
};
};
@@ -397,13 +402,12 @@ const InfrastructureDetails = () => {
const buildAreaChartConfigs = (checks) => {
let latestCheck = checks[0] ?? null;
if (latestCheck === null) return [];
const reversedChecks = checks.toReversed();
const tempData = buildTemps(reversedChecks);
const { temps, tempKeys } = buildTemps(checks);
return [
{
type: "memory",
data: reversedChecks,
dataKeys: ["memory.usage_percent"],
data: checks,
dataKeys: ["avgMemoryUsage"],
heading: "Memory usage",
strokeColor: theme.palette.primary.main,
gradientStartColor: theme.palette.primary.main,
@@ -414,15 +418,15 @@ const InfrastructureDetails = () => {
toolTip: (
<InfrastructureTooltip
dotColor={theme.palette.primary.main}
yKey={"memory.usage_percent"}
yKey={"avgMemoryUsage"}
yLabel={"Memory usage"}
/>
),
},
{
type: "cpu",
data: reversedChecks,
dataKeys: ["cpu.usage_percent"],
data: checks,
dataKeys: ["avgCpuUsage"],
heading: "CPU usage",
strokeColor: theme.palette.success.main,
gradientStartColor: theme.palette.success.main,
@@ -433,15 +437,15 @@ const InfrastructureDetails = () => {
toolTip: (
<InfrastructureTooltip
dotColor={theme.palette.success.main}
yKey={"cpu.usage_percent"}
yKey={"avgCpuUsage"}
yLabel={"CPU usage"}
/>
),
},
{
type: "temperature",
data: tempData.temps,
dataKeys: tempData.tempKeys,
data: temps,
dataKeys: tempKeys,
strokeColor: theme.palette.error.main,
gradientStartColor: theme.palette.error.main,
heading: "CPU Temperature",
@@ -450,24 +454,22 @@ const InfrastructureDetails = () => {
yDomain: [
0,
Math.max(
Math.max(
...tempData.temps.flatMap((t) => tempData.tempKeys.map((k) => t[k]))
) * 1.1,
Math.max(...temps.flatMap((t) => tempKeys.map((k) => t[k]))) * 1.1,
200
),
],
toolTip: (
<TemperatureTooltip
keys={tempData.tempKeys}
keys={tempKeys}
dotColor={theme.palette.error.main}
/>
),
},
...(latestCheck?.disk?.map((disk, idx) => ({
...(latestCheck?.disks?.map((disk, idx) => ({
type: "disk",
data: reversedChecks,
data: checks,
diskIndex: idx,
dataKeys: [`disk[${idx}].usage_percent`],
dataKeys: [`disks[${idx}].usagePercent`],
heading: `Disk${idx} usage`,
strokeColor: theme.palette.warning.main,
gradientStartColor: theme.palette.warning.main,
@@ -478,7 +480,7 @@ const InfrastructureDetails = () => {
toolTip: (
<InfrastructureTooltip
dotColor={theme.palette.warning.main}
yKey={`disk.usage_percent`}
yKey={`disks.usagePercent`}
yLabel={"Disc usage"}
yIdx={idx}
/>
@@ -491,15 +493,12 @@ const InfrastructureDetails = () => {
useEffect(() => {
const fetchData = async () => {
try {
const response = await networkService.getStatsByMonitorId({
const response = await networkService.getHardwareDetailsByMonitorId({
authToken: authToken,
monitorId: monitorId,
sortOrder: null,
limit: null,
dateRange: dateRange,
numToDisplay: 50,
normalize: false,
});
response.data.data;
setMonitor(response.data.data);
} catch (error) {
navigate("/not-found", { replace: true });
@@ -510,16 +509,17 @@ const InfrastructureDetails = () => {
}, [authToken, monitorId, dateRange, navigate]);
const statBoxConfigs = buildStatBoxes(
monitor?.checks ?? [],
monitor?.stats ?? {},
monitor?.uptimePercentage ?? "Unknown"
);
const gaugeBoxConfigs = buildGaugeBoxConfigs(monitor?.checks ?? []);
const areaChartConfigs = buildAreaChartConfigs(monitor?.checks ?? []);
const gaugeBoxConfigs = buildGaugeBoxConfigs(monitor?.stats ?? {});
const areaChartConfigs = buildAreaChartConfigs(monitor?.stats?.checks ?? []);
const lastChecked =
Date.now() - new Date(monitor?.stats?.aggregateData?.latestCheck?.createdAt);
return (
<Box>
<Breadcrumbs list={navList} />
{monitor?.checks?.length > 0 ? (
{monitor?.stats.checks?.length > 0 ? (
<Stack
direction="column"
gap={theme.spacing(10)}
@@ -545,10 +545,11 @@ const InfrastructureDetails = () => {
Checking every {formatDurationRounded(monitor?.interval)}
</Typography>
<Typography alignSelf="end">
Last checked {formatDurationSplit(monitor?.lastChecked).time}{" "}
{formatDurationSplit(monitor?.lastChecked).format} ago
Last checked {formatDurationSplit(lastChecked).time}{" "}
{formatDurationSplit(lastChecked).format} ago
</Typography>
</Stack>
<Stack
direction="row"
flexWrap="wrap"
@@ -561,21 +562,64 @@ const InfrastructureDetails = () => {
/>
))}
</Stack>
<Stack
direction="row"
gap={theme.spacing(8)}
>
{gaugeBoxConfigs.map((config) => (
<GaugeBox
key={`${config.type}-${config.diskIndex ?? ""}`}
value={config.value}
heading={config.heading}
metricOne={config.metricOne}
valueOne={config.valueOne}
metricTwo={config.metricTwo}
valueTwo={config.valueTwo}
/>
))}
{gaugeBoxConfigs.map((config) => {
return (
<GaugeBox
key={`${config.type}-${config.diskIndex ?? ""}`}
value={config.value}
heading={config.heading}
metricOne={config.metricOne}
valueOne={config.valueOne}
metricTwo={config.metricTwo}
valueTwo={config.valueTwo}
/>
);
})}
</Stack>
<Stack
direction="row"
justifyContent="space-between"
alignItems="flex-end"
gap={theme.spacing(8)}
mb={theme.spacing(8)}
>
<Typography variant="body2">
Showing statistics for past{" "}
{dateRange === "day"
? "24 hours"
: dateRange === "week"
? "7 days"
: "30 days"}
.
</Typography>
<ButtonGroup sx={{ height: 32 }}>
<Button
variant="group"
filled={(dateRange === "day").toString()}
onClick={() => setDateRange("day")}
>
Day
</Button>
<Button
variant="group"
filled={(dateRange === "week").toString()}
onClick={() => setDateRange("week")}
>
Week
</Button>
<Button
variant="group"
filled={(dateRange === "month").toString()}
onClick={() => setDateRange("month")}
>
Month
</Button>
</ButtonGroup>
</Stack>
<Stack
direction={"row"}
@@ -602,7 +646,7 @@ const InfrastructureDetails = () => {
height={areaChartHeight}
data={config.data}
dataKeys={config.dataKeys}
xKey="createdAt"
xKey="_id"
yDomain={config.yDomain}
customTooltip={config.toolTip}
xTick={config.xTick}
@@ -0,0 +1,42 @@
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "../../../../Utils/timeUtils";
const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D";
return (
<>
<text
x={x}
y={height}
dy={-3}
textAnchor="start"
fontSize={11}
>
{formatDateWithTz(firstDataPoint._id, dateFormat, uiTimezone)}
</text>
<text
x={width}
y={height}
dy={-3}
textAnchor="end"
fontSize={11}
>
{formatDateWithTz(lastDataPoint._id, dateFormat, uiTimezone)}
</text>
</>
);
};
CustomLabels.propTypes = {
x: PropTypes.number.isRequired,
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
firstDataPoint: PropTypes.object.isRequired,
lastDataPoint: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
};
export default CustomLabels;
@@ -0,0 +1,89 @@
import { memo, useState } from "react";
import { useTheme } from "@mui/material";
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
import PropTypes from "prop-types";
import CustomLabels from "./CustomLabels";
const DownBarChart = memo(({ stats, type, onBarHover }) => {
const theme = useTheme();
const [chartHovered, setChartHovered] = useState(false);
const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
return (
<ResponsiveContainer
width="100%"
minWidth={250}
height={155}
>
<BarChart
width="100%"
height="100%"
data={stats.downChecks}
onMouseEnter={() => {
setChartHovered(true);
onBarHover({ time: null, totalChecks: 0 });
}}
onMouseLeave={() => {
setChartHovered(false);
setHoveredBarIndex(null);
onBarHover(null);
}}
>
<XAxis
stroke={theme.palette.border.dark}
height={15}
tick={false}
label={
<CustomLabels
x={0}
y={0}
width="100%"
height="100%"
firstDataPoint={stats.downChecks?.[0] ?? {}}
lastDataPoint={stats.downChecks?.[stats.downChecks.length - 1] ?? {}}
type={type}
/>
}
/>
<Bar
dataKey="avgResponseTime"
maxBarSize={7}
background={{ fill: "transparent" }}
>
{stats.downChecks.map((entry, index) => (
<Cell
key={`cell-${entry.time}`}
fill={
hoveredBarIndex === index
? theme.palette.error.main
: chartHovered
? theme.palette.error.light
: theme.palette.error.main
}
onMouseEnter={() => {
setHoveredBarIndex(index);
onBarHover(entry);
}}
onMouseLeave={() => {
setHoveredBarIndex(null);
onBarHover({ time: null, totalChecks: 0 });
}}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
);
});
DownBarChart.displayName = "DownBarChart";
DownBarChart.propTypes = {
stats: PropTypes.shape({
downChecks: PropTypes.arrayOf(PropTypes.object),
downChecksAggregate: PropTypes.object,
}),
type: PropTypes.string,
onBarHover: PropTypes.func,
};
export default DownBarChart;
@@ -0,0 +1,117 @@
import PropTypes from "prop-types";
import { useTheme } from "@mui/material";
import { ResponsiveContainer, RadialBarChart, RadialBar, Cell } from "recharts";
const ResponseGaugeChart = ({ avgResponseTime }) => {
const theme = useTheme();
let max = 1000; // max ms
const data = [
{ response: max, fill: "transparent", background: false },
{ response: avgResponseTime, background: true },
];
let responseTime = Math.floor(avgResponseTime);
let responseProps =
responseTime <= 200
? {
category: "Excellent",
main: theme.palette.success.main,
bg: theme.palette.success.contrastText,
}
: responseTime <= 500
? {
category: "Fair",
main: theme.palette.success.main,
bg: theme.palette.success.contrastText,
}
: responseTime <= 600
? {
category: "Acceptable",
main: theme.palette.warning.main,
bg: theme.palette.warning.dark,
}
: {
category: "Poor",
main: theme.palette.error.main,
bg: theme.palette.error.light,
};
return (
<ResponsiveContainer
width="100%"
minWidth={210}
height={155}
>
<RadialBarChart
width="100%"
height="100%"
cy="89%"
data={data}
startAngle={180}
endAngle={0}
innerRadius={100}
outerRadius={150}
>
<text
x={0}
y="100%"
dx="5%"
dy={-2}
textAnchor="start"
fontSize={11}
>
low
</text>
<text
x="100%"
y="100%"
dx="-3%"
dy={-2}
textAnchor="end"
fontSize={11}
>
high
</text>
<text
x="50%"
y="45%"
textAnchor="middle"
dominantBaseline="middle"
fontSize={18}
fontWeight={400}
>
{responseProps.category}
</text>
<text
x="50%"
y="55%"
textAnchor="middle"
dominantBaseline="hanging"
fontSize={25}
>
<tspan fontWeight={600}>{responseTime}</tspan> <tspan opacity={0.8}>ms</tspan>
</text>
<RadialBar
background={{ fill: responseProps.bg }}
clockWise
dataKey="response"
stroke="none"
>
<Cell
fill="transparent"
background={false}
barSize={0}
/>
<Cell fill={responseProps.main} />
</RadialBar>
</RadialBarChart>
</ResponsiveContainer>
);
};
ResponseGaugeChart.propTypes = {
avgResponseTime: PropTypes.number.isRequired,
};
export default ResponseGaugeChart;
@@ -0,0 +1,100 @@
import { memo, useState } from "react";
import { useTheme } from "@mui/material";
import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts";
import PropTypes from "prop-types";
import CustomLabels from "./CustomLabels";
const UpBarChart = memo(({ stats, type, onBarHover }) => {
const theme = useTheme();
const [chartHovered, setChartHovered] = useState(false);
const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
const getColorRange = (responseTime) => {
return responseTime < 200
? { main: theme.palette.success.main, light: theme.palette.success.light }
: responseTime < 300
? { main: theme.palette.warning.main, light: theme.palette.warning.light }
: { main: theme.palette.error.main, light: theme.palette.error.light };
};
return (
<ResponsiveContainer
width="100%"
minWidth={210}
height={155}
>
<BarChart
width="100%"
height="100%"
data={stats.upChecks}
onMouseEnter={() => {
setChartHovered(true);
onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 });
}}
onMouseLeave={() => {
setChartHovered(false);
setHoveredBarIndex(null);
onBarHover(null);
}}
>
<XAxis
stroke={theme.palette.border.dark}
height={15}
tick={false}
label={
<CustomLabels
x={0}
y={0}
width="100%"
height="100%"
firstDataPoint={stats.upChecks[0]}
lastDataPoint={stats.upChecks[stats.upChecks.length - 1]}
type={type}
/>
}
/>
<Bar
dataKey="avgResponseTime"
maxBarSize={7}
background={{ fill: "transparent" }}
>
{stats.upChecks.map((entry, index) => {
let { main, light } = getColorRange(entry.avgResponseTime);
return (
<Cell
key={`cell-${entry.time}`}
fill={hoveredBarIndex === index ? main : chartHovered ? light : main}
onMouseEnter={() => {
setHoveredBarIndex(index);
onBarHover(entry);
}}
onMouseLeave={() => {
setHoveredBarIndex(null);
onBarHover({
time: null,
totalChecks: 0,
groupUptimePercentage: 0,
});
}}
/>
);
})}
</Bar>
</BarChart>
</ResponsiveContainer>
);
});
// Add display name for the component
UpBarChart.displayName = "UpBarChart";
// Validate props using PropTypes
UpBarChart.propTypes = {
stats: PropTypes.shape({
upChecks: PropTypes.array,
upChecksAggregate: PropTypes.object,
}),
type: PropTypes.string,
onBarHover: PropTypes.func,
};
export default UpBarChart;
@@ -1,346 +0,0 @@
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import {
BarChart,
Bar,
XAxis,
ResponsiveContainer,
Cell,
RadialBarChart,
RadialBar,
} from "recharts";
import { memo, useMemo, useState } from "react";
import { useSelector } from "react-redux";
import { formatDateWithTz } from "../../../../Utils/timeUtils";
const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D";
return (
<>
<text
x={x}
y={height}
dy={-3}
textAnchor="start"
fontSize={11}
>
{formatDateWithTz(new Date(firstDataPoint.time), dateFormat, uiTimezone)}
</text>
<text
x={width}
y={height}
dy={-3}
textAnchor="end"
fontSize={11}
>
{formatDateWithTz(new Date(lastDataPoint.time), dateFormat, uiTimezone)}
</text>
</>
);
};
CustomLabels.propTypes = {
x: PropTypes.number.isRequired,
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
firstDataPoint: PropTypes.object.isRequired,
lastDataPoint: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
};
const UpBarChart = memo(({ data, type, onBarHover }) => {
const theme = useTheme();
const [chartHovered, setChartHovered] = useState(false);
const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
const getColorRange = (uptime) => {
return uptime > 80
? { main: theme.palette.success.main, light: theme.palette.success.light }
: uptime > 50
? { main: theme.palette.warning.main, light: theme.palette.warning.light }
: { main: theme.palette.error.contrastText, light: theme.palette.error.light };
};
// TODO - REMOVE THIS LATER
const reversedData = useMemo(() => [...data].reverse(), [data]);
return (
<ResponsiveContainer
width="100%"
minWidth={210}
height={155}
>
<BarChart
width="100%"
height="100%"
data={reversedData}
onMouseEnter={() => {
setChartHovered(true);
onBarHover({ time: null, totalChecks: 0, uptimePercentage: 0 });
}}
onMouseLeave={() => {
setChartHovered(false);
setHoveredBarIndex(null);
onBarHover(null);
}}
>
<XAxis
stroke={theme.palette.border.dark}
height={15}
tick={false}
label={
<CustomLabels
x={0}
y={0}
width="100%"
height="100%"
firstDataPoint={reversedData[0]}
lastDataPoint={reversedData[reversedData.length - 1]}
type={type}
/>
}
/>
<Bar
dataKey="totalChecks"
maxBarSize={7}
background={{ fill: "transparent" }}
>
{reversedData.map((entry, index) => {
let { main, light } = getColorRange(entry.uptimePercentage);
return (
<Cell
key={`cell-${entry.time}`}
fill={hoveredBarIndex === index ? main : chartHovered ? light : main}
onMouseEnter={() => {
setHoveredBarIndex(index);
onBarHover(entry);
}}
onMouseLeave={() => {
setHoveredBarIndex(null);
onBarHover({
time: null,
totalChecks: 0,
uptimePercentage: 0,
});
}}
/>
);
})}
</Bar>
</BarChart>
</ResponsiveContainer>
);
});
// Add display name for the component
UpBarChart.displayName = "UpBarChart";
// Validate props using PropTypes
UpBarChart.propTypes = {
data: PropTypes.arrayOf(PropTypes.object),
type: PropTypes.string,
onBarHover: PropTypes.func,
};
export { UpBarChart };
const DownBarChart = memo(({ data, type, onBarHover }) => {
const theme = useTheme();
const [chartHovered, setChartHovered] = useState(false);
const [hoveredBarIndex, setHoveredBarIndex] = useState(null);
// TODO - REMOVE THIS LATER
const reversedData = useMemo(() => [...data].reverse(), [data]);
return (
<ResponsiveContainer
width="100%"
minWidth={250}
height={155}
>
<BarChart
width="100%"
height="100%"
data={reversedData}
onMouseEnter={() => {
setChartHovered(true);
onBarHover({ time: null, totalIncidents: 0 });
}}
onMouseLeave={() => {
setChartHovered(false);
setHoveredBarIndex(null);
onBarHover(null);
}}
>
<XAxis
stroke={theme.palette.border.dark}
height={15}
tick={false}
label={
<CustomLabels
x={0}
y={0}
width="100%"
height="100%"
firstDataPoint={reversedData[0]}
lastDataPoint={reversedData[reversedData.length - 1]}
type={type}
/>
}
/>
<Bar
dataKey="totalIncidents"
maxBarSize={7}
background={{ fill: "transparent" }}
>
{reversedData.map((entry, index) => (
<Cell
key={`cell-${entry.time}`}
fill={
hoveredBarIndex === index
? theme.palette.error.contrastText
: chartHovered
? theme.palette.error.light
: theme.palette.error.contrastText
}
onMouseEnter={() => {
setHoveredBarIndex(index);
onBarHover(entry);
}}
onMouseLeave={() => {
setHoveredBarIndex(null);
onBarHover({ time: null, totalIncidents: 0 });
}}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
);
});
DownBarChart.displayName = "DownBarChart";
DownBarChart.propTypes = {
data: PropTypes.arrayOf(PropTypes.object),
type: PropTypes.string,
onBarHover: PropTypes.func,
};
export { DownBarChart };
const ResponseGaugeChart = ({ data }) => {
const theme = useTheme();
let max = 1000; // max ms
const memoizedData = useMemo(
() => [{ response: max, fill: "transparent", background: false }, ...data],
[data[0].response]
);
let responseTime = Math.floor(memoizedData[1].response);
let responseProps =
responseTime <= 200
? {
category: "Excellent",
main: theme.palette.success.main,
bg: theme.palette.success.contrastText,
}
: responseTime <= 500
? {
category: "Fair",
main: theme.palette.success.main,
bg: theme.palette.success.contrastText,
}
: responseTime <= 600
? {
category: "Acceptable",
main: theme.palette.warning.main,
bg: theme.palette.warning.dark,
}
: {
category: "Poor",
main: theme.palette.error.light,
bg: theme.palette.error.dark,
};
return (
<ResponsiveContainer
width="100%"
minWidth={210}
height={155}
>
<RadialBarChart
width="100%"
height="100%"
cy="89%"
data={memoizedData}
startAngle={180}
endAngle={0}
innerRadius={100}
outerRadius={150}
>
<text
x={0}
y="100%"
dx="5%"
dy={-2}
textAnchor="start"
fontSize={11}
>
low
</text>
<text
x="100%"
y="100%"
dx="-3%"
dy={-2}
textAnchor="end"
fontSize={11}
>
high
</text>
<text
x="50%"
y="45%"
textAnchor="middle"
dominantBaseline="middle"
fontSize={18}
fontWeight={400}
>
{responseProps.category}
</text>
<text
x="50%"
y="55%"
textAnchor="middle"
dominantBaseline="hanging"
fontSize={25}
>
<tspan fontWeight={600}>{responseTime}</tspan> <tspan opacity={0.8}>ms</tspan>
</text>
<RadialBar
background={{ fill: responseProps.bg }}
clockWise
dataKey="response"
stroke="none"
>
<Cell
fill="transparent"
background={false}
barSize={0}
/>
<Cell fill={responseProps.main} />
</RadialBar>
</RadialBarChart>
</ResponsiveContainer>
);
};
ResponseGaugeChart.propTypes = {
data: PropTypes.arrayOf(PropTypes.object).isRequired,
};
export { ResponseGaugeChart };
+32 -25
View File
@@ -5,7 +5,6 @@ import { useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { networkService } from "../../../main";
import { logger } from "../../../Utils/Logger";
import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils";
import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart";
import ButtonGroup from "@mui/material/ButtonGroup";
import SettingsIcon from "../../../assets/icons/settings-bold.svg?react";
@@ -18,14 +17,16 @@ import PaginationTable from "./PaginationTable";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import PulseDot from "../../../Components/Animated/PulseDot";
import { ChartBox } from "./styled";
import { DownBarChart, ResponseGaugeChart, UpBarChart } from "./Charts";
import SkeletonLayout from "./skeleton";
import "./index.css";
import useUtils from "../utils";
import { formatDateWithTz } from "../../../Utils/timeUtils";
import { formatDateWithTz, formatDurationSplit } from "../../../Utils/timeUtils";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import IconBox from "../../../Components/IconBox";
import StatBox from "../../../Components/StatBox";
import UpBarChart from "./Charts/UpBarChart";
import DownBarChart from "./Charts/DownBarChart";
import ResponseGaugeChart from "./Charts/ResponseGaugeChart";
/**
* Details page component displaying monitor details and related information.
* @component
@@ -47,16 +48,12 @@ const DetailsPage = () => {
const fetchMonitor = useCallback(async () => {
try {
const res = await networkService.getStatsByMonitorId({
const res = await networkService.getUptimeDetailsById({
authToken: authToken,
monitorId: monitorId,
sortOrder: null,
limit: null,
dateRange: dateRange,
numToDisplay: 50,
normalize: true,
});
console.log(res?.data?.data);
setMonitor(res?.data?.data ?? {});
} catch (error) {
logger.error(error);
@@ -185,7 +182,7 @@ const DetailsPage = () => {
},
}}
>
Checking every {formatDurationRounded(monitor?.interval)}.
{/* Checking every {formatDurationRounded(monitor?.interval)}. */}
</Typography>
</Stack>
</Box>
@@ -224,17 +221,17 @@ const DetailsPage = () => {
<StatBox
sx={statusStyles[determineState(monitor)]}
heading={"active for"}
subHeading={splitDuration(monitor?.uptimeDuration)}
subHeading={splitDuration(monitor?.stats?.timeSinceLastFalseCheck)}
/>
<StatBox
heading="last check"
subHeading={splitDuration(monitor?.lastChecked)}
subHeading={splitDuration(monitor?.stats?.timeSinceLastCheck)}
/>
<StatBox
heading="last response time"
subHeading={
<>
{monitor?.latestResponseTime}
{monitor?.stats?.latestResponseTime}
<Typography component="span">{"ms"}</Typography>
</>
}
@@ -311,7 +308,7 @@ const DetailsPage = () => {
<Typography component="span">
{hoveredUptimeData !== null
? hoveredUptimeData.totalChecks
: monitor?.periodTotalChecks}
: (monitor.stats?.upChecksAggregate?.totalChecks ?? 0)}
</Typography>
{hoveredUptimeData !== null && hoveredUptimeData.time !== null && (
<Typography
@@ -322,7 +319,7 @@ const DetailsPage = () => {
color={theme.palette.text.tertiary}
>
{formatDateWithTz(
hoveredUptimeData.time,
hoveredUptimeData._id,
dateFormat,
uiTimezone
)}
@@ -330,17 +327,27 @@ const DetailsPage = () => {
)}
</Box>
<Box>
<Typography>Uptime Percentage</Typography>
<Typography>
{hoveredUptimeData !== null
? "Avg Response Time"
: "Uptime Percentage"}
</Typography>
<Typography component="span">
{hoveredUptimeData !== null
? Math.floor(hoveredUptimeData.uptimePercentage * 10) / 10
: Math.floor(monitor?.periodUptime * 10) / 10}
<Typography component="span">%</Typography>
? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0)
: Math.floor(
((monitor?.stats?.upChecksAggregate?.totalChecks ?? 0) /
(monitor?.stats?.totalChecks ?? 1)) *
100
)}
<Typography component="span">
{hoveredUptimeData !== null ? " ms" : " %"}
</Typography>
</Typography>
</Box>
</Stack>
<UpBarChart
data={monitor?.aggregateData}
stats={monitor?.stats}
type={dateRange}
onBarHover={setHoveredUptimeData}
/>
@@ -356,8 +363,8 @@ const DetailsPage = () => {
<Typography>Total Incidents</Typography>
<Typography component="span">
{hoveredIncidentsData !== null
? hoveredIncidentsData.totalIncidents
: monitor?.periodIncidents}
? hoveredIncidentsData.totalChecks
: (monitor.stats?.downChecksAggregate?.totalChecks ?? 0)}
</Typography>
{hoveredIncidentsData !== null &&
hoveredIncidentsData.time !== null && (
@@ -369,7 +376,7 @@ const DetailsPage = () => {
color={theme.palette.text.tertiary}
>
{formatDateWithTz(
hoveredIncidentsData.time,
hoveredIncidentsData._id,
dateFormat,
uiTimezone
)}
@@ -377,7 +384,7 @@ const DetailsPage = () => {
)}
</Box>
<DownBarChart
data={monitor?.aggregateData}
stats={monitor?.stats}
type={dateRange}
onBarHover={setHoveredIncidentsData}
/>
@@ -390,7 +397,7 @@ const DetailsPage = () => {
<Typography component="h2">Average Response Time</Typography>
</Stack>
<ResponseGaugeChart
data={[{ response: monitor?.periodAvgResponseTime }]}
avgResponseTime={monitor?.stats?.groupAggregate?.avgResponseTime ?? 0}
/>
</ChartBox>
<ChartBox sx={{ padding: 0 }}>
@@ -403,7 +410,7 @@ const DetailsPage = () => {
</IconBox>
<Typography component="h2">Response Times</Typography>
</Stack>
<MonitorDetailsAreaChart checks={[...monitor.checks].reverse()} />
<MonitorDetailsAreaChart checks={monitor?.stats?.groupChecks ?? []} />
</ChartBox>
<ChartBox
gap={theme.spacing(8)}
+28
View File
@@ -242,6 +242,34 @@ class NetworkService {
);
}
async getHardwareDetailsByMonitorId(config) {
const params = new URLSearchParams();
if (config.dateRange) params.append("dateRange", config.dateRange);
return this.axiosInstance.get(
`/monitors/hardware/details/${config.monitorId}?${params.toString()}`,
{
headers: {
Authorization: `Bearer ${config.authToken}`,
},
}
);
}
async getUptimeDetailsById(config) {
const params = new URLSearchParams();
if (config.dateRange) params.append("dateRange", config.dateRange);
if (config.normalize) params.append("normalize", config.normalize);
return this.axiosInstance.get(
`/monitors/uptime/details/${config.monitorId}?${params.toString()}`,
{
headers: {
Authorization: `Bearer ${config.authToken}`,
},
}
);
}
/**
* ************************************
* Updates a single monitor
+4 -1
View File
@@ -1,8 +1,11 @@
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import customParseFormat from "dayjs/plugin/customParseFormat";
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(customParseFormat);
export const MS_PER_SECOND = 1000;
export const MS_PER_MINUTE = 60 * MS_PER_SECOND;
@@ -90,6 +93,6 @@ export const formatDate = (date, customOptions) => {
};
export const formatDateWithTz = (timestamp, format, timezone) => {
const formattedDate = dayjs(timestamp, timezone).tz(timezone).format(format);
const formattedDate = dayjs(timestamp).tz(timezone).format(format);
return formattedDate;
};
+10 -10
View File
@@ -1,10 +1,9 @@
**Need support or have a suggestion? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum.**
**Checkmate is on [GitHub trending](https://github.com/trending) and #1 trending for JavaScript apps on Fri Dec 13th & 15th**
# **We're opening our $5000 grant funding announcement soon, powered by Checkmate and [UpRock](https://uprock.com) - check [our web page](https://checkmate.so) for preliminary details.**
**If you would like to support us, please consider giving it a ⭐, and think about contributing or providing feedback.**
**If you would like to support us, please consider giving it a ⭐, and think about contributing or providing feedback. Need support or have a suggestion? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum.**
![Frame 34](https://github.com/user-attachments/assets/4bf57845-3f47-4759-835b-285a5486191d)
<img width="1259" alt="Frame 34" src="https://github.com/user-attachments/assets/d491a734-fd7a-4841-9f84-fb5bef5ad586" />
![](https://img.shields.io/github/license/bluewave-labs/checkmate)
@@ -53,20 +52,21 @@ If you have any questions, suggestions or comments, please use our [Discord chan
- Infrastructure monitoring (memory, disk usage, CPU performance etc) - requires [Capture](https://github.com/bluewave-labs/capture)
- Docker monitoring
- Ping monitoring
- SSL monitoring
- Incidents at a glance
- E-mail notifications
- Scheduled maintenance
- Can monitor 1000+ servers at the same time on a moderate server
**Short term roadmap:**
- Global (distributed) uptime checking on Solana network
- Status pages
- Better notification options (think Discord, Telegram, Slack)
- Port monitoring (**complete**, waiting to be deployed to stable version)
- Global (distributed) uptime checking on Solana network (**in progress**)
- Status pages (**in progress**)
- Better notification options (Webhooks, Discord, Telegram, Slack)
- More configuration options
- Translations
- Tagging/grouping monitors
- DNS monitoring
- SSL monitoring
- Port monitoring
## 🏗️ Screenshots
-2
View File
@@ -8,14 +8,12 @@ import {
newPasswordValidation,
} from "../validation/joi.js";
import logger from "../utils/logger.js";
import dotenv from "dotenv";
import { errorMessages, successMessages } from "../utils/messages.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders, tokenType } from "../utils/utils.js";
import crypto from "crypto";
import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "authController";
dotenv.config();
class AuthController {
constructor(db, settingsService, emailService, jobQueue) {
@@ -0,0 +1,18 @@
import { handleError } from "./controllerUtils.js";
import { successMessages } from "../utils/messages.js";
const SERVICE_NAME = "DistributedUptimeQueueController";
class DistributedUptimeController {
constructor() {}
async resultsCallback(req, res, next) {
try {
console.log(req.body);
res.status(200).json({ message: "OK" });
} catch (error) {
throw handleError(error, SERVICE_NAME, "resultsCallback");
}
}
}
export default DistributedUptimeController;
+44
View File
@@ -12,6 +12,8 @@ import {
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,
getCertificateParamValidation,
getHardwareDetailsByIdParamValidation,
getHardwareDetailsByIdQueryValidation,
} from "../validation/joi.js";
import sslChecker from "ssl-checker";
import { successMessages } from "../utils/messages.js";
@@ -74,6 +76,19 @@ class MonitorController {
}
};
getUptimeDetailsById = async (req, res, next) => {
try {
const monitor = await this.db.getUptimeDetailsById(req);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_ID,
data: monitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorDetailsById"));
}
};
/**
* Returns monitor stats for monitor with matching ID
* @async
@@ -104,6 +119,35 @@ class MonitorController {
}
};
/**
* Get hardware details for a specific monitor by ID
* @async
* @param {Express.Request} req - Express request object containing monitorId in params
* @param {Express.Response} res - Express response object
* @param {Express.NextFunction} next - Express next middleware function
* @returns {Promise<Express.Response>}
* @throws {Error} - Throws error if monitor not found or other database errors
*/
getHardwareDetailsById = async (req, res, next) => {
try {
await getHardwareDetailsByIdParamValidation.validateAsync(req.params);
await getHardwareDetailsByIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const monitor = await this.db.getHardwareDetailsById(req);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_ID,
data: monitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getHardwareDetailsById"));
}
};
getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) => {
try {
await getCertificateParamValidation.validateAsync(req.params);
+3
View File
@@ -67,5 +67,8 @@ const BaseCheckSchema = mongoose.Schema({
*/
const CheckSchema = mongoose.Schema({ ...BaseCheckSchema.obj }, { timestamps: true });
CheckSchema.index({ createdAt: 1 });
CheckSchema.index({ monitorId: 1, createdAt: 1 });
CheckSchema.index({ monitorId: 1, createdAt: -1 });
export default mongoose.model("Check", CheckSchema);
export { BaseCheckSchema };
@@ -0,0 +1,15 @@
import mongoose from "mongoose";
import { BaseCheckSchema } from "./Check.js";
const DistributedUptimeCheckSchema = mongoose.Schema(
{
...BaseCheckSchema.obj,
},
{ timestamps: true }
);
DistributedUptimeCheckSchema.index({ createdAt: 1 });
DistributedUptimeCheckSchema.index({ monitorId: 1, createdAt: 1 });
DistributedUptimeCheckSchema.index({ monitorId: 1, createdAt: -1 });
export default mongoose.model("DistributedUptimeCheck", DistributedUptimeCheckSchema);
+2
View File
@@ -64,5 +64,7 @@ const HardwareCheckSchema = mongoose.Schema(
);
HardwareCheckSchema.index({ createdAt: 1 });
HardwareCheckSchema.index({ monitorId: 1, createdAt: 1 });
HardwareCheckSchema.index({ monitorId: 1, createdAt: -1 });
export default mongoose.model("HardwareCheck", HardwareCheckSchema);
+9 -1
View File
@@ -28,7 +28,15 @@ const MonitorSchema = mongoose.Schema(
type: {
type: String,
required: true,
enum: ["http", "ping", "pagespeed", "hardware", "docker", "port"],
enum: [
"http",
"ping",
"pagespeed",
"hardware",
"docker",
"port",
"distributed_http",
],
},
url: {
type: String,
+2
View File
@@ -105,5 +105,7 @@ PageSpeedCheck.pre("save", async function (next) {
});
PageSpeedCheck.index({ createdAt: 1 });
PageSpeedCheck.index({ monitorId: 1, createdAt: 1 });
PageSpeedCheck.index({ monitorId: 1, createdAt: -1 });
export default mongoose.model("PageSpeedCheck", PageSpeedCheck);
+2
View File
@@ -84,6 +84,8 @@ const User = mongoose.model("User", UserSchema);
User.init().then(() => {
logger.info({
message: "User model initialized",
service: "UserModel",
method: "init",
});
});
+12 -1
View File
@@ -91,7 +91,18 @@ class MongoDB {
appSettings = new AppSettings({});
await appSettings.save();
}
logger.info({ message: "Connected to MongoDB" });
// Sync indexes
const models = mongoose.modelNames();
for (const modelName of models) {
const model = mongoose.model(modelName);
await model.syncIndexes();
}
logger.info({
message: "Connected to MongoDB",
service: this.SERVICE_NAME,
method: "connect",
});
} catch (error) {
logger.error({
message: error.message,
@@ -47,7 +47,6 @@ const createHardwareCheck = async (hardwareCheckData) => {
const deleteHardwareChecksByMonitorId = async (monitorId) => {
try {
const result = await HardwareCheck.deleteMany({ monitorId });
console.log("deleted hardware checks", result);
return result.deletedCount;
} catch (error) {
error.service = SERVICE_NAME;
+615 -3
View File
@@ -4,7 +4,7 @@ import PageSpeedCheck from "../../models/PageSpeedCheck.js";
import HardwareCheck from "../../models/HardwareCheck.js";
import { errorMessages } from "../../../utils/messages.js";
import Notification from "../../models/Notification.js";
import { NormalizeData } from "../../../utils/dataUtils.js";
import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUtils.js";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
@@ -224,15 +224,22 @@ const getDateRange = (dateRange) => {
* @returns {Promise<Object>} All checks and date-ranged checks
*/
const getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => {
const indexSpec = {
monitorId: 1,
createdAt: sortOrder, // This will be 1 or -1
};
const [checksAll, checksForDateRange] = await Promise.all([
model.find({ monitorId }).sort({ createdAt: sortOrder }),
model.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(),
model
.find({
monitorId,
createdAt: { $gte: dateRange.start, $lte: dateRange.end },
})
.sort({ createdAt: sortOrder }),
.hint(indexSpec)
.lean(),
]);
return { checksAll, checksForDateRange };
};
@@ -304,6 +311,329 @@ const calculateGroupStats = (group) => {
};
};
/**
* Get uptime details by monitor ID
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Monitor>}
* @throws {Error}
*/
const getUptimeDetailsById = async (req) => {
try {
const { monitorId } = req.params;
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
}
const { dateRange, normalize } = req.query;
const dates = getDateRange(dateRange);
const formatLookup = {
day: "%Y-%m-%dT%H:00:00Z",
week: "%Y-%m-%dT%H:00:00Z",
month: "%Y-%m-%dT00:00:00Z",
};
const dateString = formatLookup[dateRange];
const monitorData = await Check.aggregate([
{
$match: {
monitorId: monitor._id,
},
},
{
$sort: {
createdAt: 1,
},
},
{
$facet: {
aggregateData: [
{
$group: {
_id: null,
avgResponseTime: {
$avg: "$responseTime",
},
firstCheck: {
$first: "$$ROOT",
},
lastCheck: {
$last: "$$ROOT",
},
totalChecks: {
$sum: 1,
},
},
},
],
uptimeDuration: [
{
$match: {
status: false,
},
},
{
$sort: {
createdAt: 1,
},
},
{
$group: {
_id: null,
lastFalseCheck: {
$last: "$$ROOT",
},
},
},
],
groupChecks: [
{
$match: {
createdAt: { $gte: dates.start, $lte: dates.end },
},
},
{
$group: {
_id: {
$dateToString: {
format: dateString,
date: "$createdAt",
},
},
avgResponseTime: {
$avg: "$responseTime",
},
totalChecks: {
$sum: 1,
},
},
},
{
$sort: {
_id: 1,
},
},
],
groupAggregate: [
{
$match: {
createdAt: { $gte: dates.start, $lte: dates.end },
},
},
{
$group: {
_id: null,
avgResponseTime: {
$avg: "$responseTime",
},
},
},
],
upChecksAggregate: [
{
$match: {
status: true,
},
},
{
$group: {
_id: null,
avgResponseTime: {
$avg: "$responseTime",
},
totalChecks: {
$sum: 1,
},
},
},
],
upChecks: [
{
$match: {
status: true,
createdAt: { $gte: dates.start, $lte: dates.end },
},
},
{
$group: {
_id: {
$dateToString: {
format: dateString,
date: "$createdAt",
},
},
totalChecks: {
$sum: 1,
},
avgResponseTime: {
$avg: "$responseTime",
},
},
},
{
$sort: { _id: 1 },
},
],
downChecksAggregate: [
{
$match: {
status: false,
},
},
{
$group: {
_id: null,
avgResponseTime: {
$avg: "$responseTime",
},
totalChecks: {
$sum: 1,
},
},
},
],
downChecks: [
{
$match: {
status: false,
createdAt: { $gte: dates.start, $lte: dates.end },
},
},
{
$group: {
_id: {
$dateToString: {
format: dateString,
date: "$createdAt",
},
},
totalChecks: {
$sum: 1,
},
avgResponseTime: {
$avg: "$responseTime",
},
},
},
{
$sort: { _id: 1 },
},
],
},
},
{
$project: {
avgResponseTime: {
$arrayElemAt: ["$aggregateData.avgResponseTime", 0],
},
totalChecks: {
$arrayElemAt: ["$aggregateData.totalChecks", 0],
},
latestResponseTime: {
$arrayElemAt: ["$aggregateData.lastCheck.responseTime", 0],
},
timeSinceLastCheck: {
$let: {
vars: {
lastCheck: {
$arrayElemAt: ["$aggregateData.lastCheck", 0],
},
},
in: {
$cond: [
{
$ifNull: ["$$lastCheck", false],
},
{
$subtract: [new Date(), "$$lastCheck.createdAt"],
},
0,
],
},
},
},
timeSinceLastFalseCheck: {
$let: {
vars: {
lastFalseCheck: {
$arrayElemAt: ["$uptimeDuration.lastFalseCheck", 0],
},
firstCheck: {
$arrayElemAt: ["$aggregateData.firstCheck", 0],
},
},
in: {
$cond: [
{
$ifNull: ["$$lastFalseCheck", false],
},
{
$subtract: [new Date(), "$$lastFalseCheck.createdAt"],
},
{
$cond: [
{
$ifNull: ["$$firstCheck", false],
},
{
$subtract: [new Date(), "$$firstCheck.createdAt"],
},
0,
],
},
],
},
},
},
groupChecks: "$groupChecks",
groupAggregate: {
$arrayElemAt: ["$groupAggregate", 0],
},
upChecksAggregate: {
$arrayElemAt: ["$upChecksAggregate", 0],
},
upChecks: "$upChecks",
downChecksAggregate: {
$arrayElemAt: ["$downChecksAggregate", 0],
},
downChecks: "$downChecks",
},
},
]);
const normalizedGroupChecks = NormalizeDataUptimeDetails(
monitorData[0].groupChecks,
10,
100
);
const monitorStats = {
...monitor.toObject(),
stats: {
avgResponseTime: monitorData[0].avgResponseTime,
totalChecks: monitorData[0].totalChecks,
timeSinceLastCheck: monitorData[0].timeSinceLastCheck,
timeSinceLastFalseCheck: monitorData[0].timeSinceLastFalseCheck,
latestResponseTime: monitorData[0].latestResponseTime,
groupChecks: normalizedGroupChecks,
groupAggregate: monitorData[0].groupAggregate,
upChecksAggregate: monitorData[0].upChecksAggregate,
upChecks: monitorData[0].upChecks,
downChecksAggregate: monitorData[0].downChecksAggregate,
downChecks: monitorData[0].downChecks,
},
};
return monitorStats;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getUptimeDetailsById";
throw error;
}
};
/**
* Get stats by monitor ID
* @async
@@ -373,6 +703,286 @@ const getMonitorStatsById = async (req) => {
}
};
const getHardwareDetailsById = async (req) => {
try {
const { monitorId } = req.params;
const { dateRange } = req.query;
const monitor = await Monitor.findById(monitorId);
const dates = getDateRange(dateRange);
const formatLookup = {
day: "%Y-%m-%dT%H:00:00Z",
week: "%Y-%m-%dT%H:00:00Z",
month: "%Y-%m-%dT00:00:00Z",
};
const dateString = formatLookup[dateRange];
const hardwareStats = await HardwareCheck.aggregate([
{
$match: {
monitorId: monitor._id,
createdAt: { $gte: dates.start, $lte: dates.end },
},
},
{
$sort: {
createdAt: 1,
},
},
{
$facet: {
aggregateData: [
{
$group: {
_id: null,
latestCheck: {
$last: "$$ROOT",
},
totalChecks: {
$sum: 1,
},
},
},
],
upChecks: [
{
$match: {
status: true,
},
},
{
$group: {
_id: null,
totalChecks: {
$sum: 1,
},
},
},
],
checks: [
{
$limit: 1,
},
{
$project: {
diskCount: {
$size: "$disk",
},
},
},
{
$lookup: {
from: "hardwarechecks",
let: {
diskCount: "$diskCount",
},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ["$monitorId", monitor._id] },
{ $gte: ["$createdAt", dates.start] },
{ $lte: ["$createdAt", dates.end] },
],
},
},
},
{
$group: {
_id: {
$dateToString: {
format: dateString,
date: "$createdAt",
},
},
avgCpuUsage: {
$avg: "$cpu.usage_percent",
},
avgMemoryUsage: {
$avg: "$memory.usage_percent",
},
avgTemperatures: {
$push: {
$ifNull: ["$cpu.temperature", [0]],
},
},
disks: {
$push: "$disk",
},
},
},
{
$project: {
_id: 1,
avgCpuUsage: 1,
avgMemoryUsage: 1,
avgTemperature: {
$map: {
input: {
$range: [
0,
{
$size: {
// Handle null temperatures array
$ifNull: [
{ $arrayElemAt: ["$avgTemperatures", 0] },
[0], // Default to single-element array if null
],
},
},
],
},
as: "index",
in: {
$avg: {
$map: {
input: "$avgTemperatures",
as: "tempArray",
in: {
$ifNull: [
{ $arrayElemAt: ["$$tempArray", "$$index"] },
0, // Default to 0 if element is null
],
},
},
},
},
},
},
disks: {
$map: {
input: {
$range: [0, "$$diskCount"],
},
as: "diskIndex",
in: {
name: {
$concat: [
"disk",
{
$toString: "$$diskIndex",
},
],
},
readSpeed: {
$avg: {
$map: {
input: "$disks",
as: "diskArray",
in: {
$arrayElemAt: [
"$$diskArray.read_speed_bytes",
"$$diskIndex",
],
},
},
},
},
writeSpeed: {
$avg: {
$map: {
input: "$disks",
as: "diskArray",
in: {
$arrayElemAt: [
"$$diskArray.write_speed_bytes",
"$$diskIndex",
],
},
},
},
},
totalBytes: {
$avg: {
$map: {
input: "$disks",
as: "diskArray",
in: {
$arrayElemAt: [
"$$diskArray.total_bytes",
"$$diskIndex",
],
},
},
},
},
freeBytes: {
$avg: {
$map: {
input: "$disks",
as: "diskArray",
in: {
$arrayElemAt: [
"$$diskArray.free_bytes",
"$$diskIndex",
],
},
},
},
},
usagePercent: {
$avg: {
$map: {
input: "$disks",
as: "diskArray",
in: {
$arrayElemAt: [
"$$diskArray.usage_percent",
"$$diskIndex",
],
},
},
},
},
},
},
},
},
},
],
as: "hourlyStats",
},
},
{
$unwind: "$hourlyStats",
},
{
$replaceRoot: {
newRoot: "$hourlyStats",
},
},
],
},
},
{
$project: {
aggregateData: {
$arrayElemAt: ["$aggregateData", 0],
},
upChecks: {
$arrayElemAt: ["$upChecks", 0],
},
checks: {
$sortArray: {
input: "$checks",
sortBy: { _id: 1 },
},
},
},
},
]);
const monitorStats = {
...monitor.toObject(),
stats: hardwareStats[0],
};
return monitorStats;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getHardwareDetailsById";
throw error;
}
};
/**
* Get a monitor by ID
* @async
@@ -652,6 +1262,7 @@ export {
getAllMonitorsWithUptimeStats,
getMonitorStatsById,
getMonitorById,
getUptimeDetailsById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
@@ -660,6 +1271,7 @@ export {
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
getHardwareDetailsById,
};
// Helper functions
+1
View File
@@ -14,6 +14,7 @@ export default [
languageOptions: {
globals: {
...globals.node, // Add Node.js globals
...globals.chai, // Add Chai globals
},
ecmaVersion: 2023,
sourceType: "module",
+54 -4
View File
@@ -34,6 +34,9 @@ import StatusPageController from "./controllers/statusPageController.js";
import QueueRoutes from "./routes/queueRoute.js";
import QueueController from "./controllers/queueController.js";
import DistributedUptimeRoutes from "./routes/distributedUptimeRoute.js";
import DistributedUptimeController from "./controllers/distributedUptimeController.js";
//JobQueue service and dependencies
import JobQueue from "./service/jobQueue.js";
import { Queue, Worker } from "bullmq";
@@ -45,7 +48,7 @@ import ping from "ping";
import http from "http";
import Docker from "dockerode";
import net from "net";
import ngrok from "ngrok";
// Email service and dependencies
import EmailService from "./service/emailService.js";
import nodemailer from "nodemailer";
@@ -68,6 +71,8 @@ import ServiceRegistry from "./service/serviceRegistry.js";
import MongoDB from "./db/mongo/MongoDB.js";
import IORedis from "ioredis";
const SERVICE_NAME = "Server";
const SHUTDOWN_TIMEOUT = 1000;
let isShuttingDown = false;
@@ -78,6 +83,7 @@ const openApiSpec = JSON.parse(
);
let server;
let ngrokUrl;
const PORT = 5000;
@@ -87,12 +93,24 @@ const shutdown = async () => {
}
isShuttingDown = true;
logger.info({ message: "Attempting graceful shutdown" });
setTimeout(() => {
setTimeout(async () => {
logger.error({
message: "Could not shut down in time, forcing shutdown",
service: SERVICE_NAME,
method: "shutdown",
});
// flush Redis
const settings =
ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings() || {};
const { redisHost = "127.0.0.1", redisPort = 6379 } = settings;
const redis = new IORedis({
host: redisHost,
port: redisPort,
});
logger.info({ message: "Flushing Redis" });
await redis.flushall();
logger.info({ message: "Redis flushed" });
process.exit(1);
}, SHUTDOWN_TIMEOUT);
try {
@@ -113,6 +131,29 @@ const shutdown = async () => {
// Need to wrap server setup in a function to handle async nature of JobQueue
const startApp = async () => {
const app = express();
if (process.env.NODE_ENV === "development") {
try {
ngrokUrl = await ngrok.connect({
proto: "http",
addr: PORT,
authtoken: process.env.NGROK_AUTH_TOKEN,
api_addr: false,
});
process.env.NGROK_URL = ngrokUrl;
logger.info({
message: `ngrok url: ${ngrokUrl}`,
service: SERVICE_NAME,
method: "startApp",
});
} catch (error) {
logger.error({
message: `Error connecting to ngrok`,
service: SERVICE_NAME,
method: "startApp",
stack: error.stack,
});
}
}
// Create DB
const db = new MongoDB();
@@ -133,10 +174,11 @@ const startApp = async () => {
const networkService = new NetworkService(axios, ping, logger, http, Docker, net);
const statusService = new StatusService(db, logger);
const notificationService = new NotificationService(emailService, db, logger);
const jobQueue = await JobQueue.createJobQueue(
const jobQueue = new JobQueue(
db,
networkService,
statusService,
networkService,
notificationService,
settingsService,
logger,
@@ -201,6 +243,8 @@ const startApp = async () => {
ServiceRegistry.get(MongoDB.SERVICE_NAME)
);
const distributedUptimeController = new DistributedUptimeController();
//Create routes
const authRoutes = new AuthRoutes(authController);
const monitorRoutes = new MonitorRoutes(monitorController);
@@ -212,6 +256,11 @@ const startApp = async () => {
);
const queueRoutes = new QueueRoutes(queueController);
const statusPageRoutes = new StatusPageRoutes(statusPageController);
const distributedUptimeRoutes = new DistributedUptimeRoutes(
distributedUptimeController
);
// Init job queue
await jobQueue.initJobQueue();
// Middleware
app.use(
cors()
@@ -230,6 +279,7 @@ const startApp = async () => {
app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
app.use("/api/v1/distributed-uptime", distributedUptimeRoutes.getRouter());
app.use("/api/v1/status-page", statusPageRoutes.getRouter());
app.use(handleErrors);
};
+447 -11
View File
@@ -11,13 +11,14 @@
"dependencies": {
"axios": "^1.7.2",
"bcrypt": "5.1.1",
"bullmq": "5.34.5",
"bullmq": "5.34.6",
"cors": "^2.8.5",
"dockerode": "4.0.2",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"handlebars": "^4.7.8",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
"joi": "^17.13.1",
"jsonwebtoken": "9.0.2",
"mailersend": "^2.2.0",
@@ -40,6 +41,7 @@
"esm": "3.2.25",
"globals": "^15.14.0",
"mocha": "11.0.1",
"ngrok": "5.0.0-beta.2",
"nodemon": "3.1.9",
"prettier": "^3.3.3",
"sinon": "19.0.2"
@@ -993,6 +995,19 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"license": "BSD-3-Clause"
},
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@sinonjs/commons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
@@ -1042,6 +1057,19 @@
"dev": true,
"license": "(Unlicense OR Apache-2.0)"
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
"dev": true,
"license": "MIT",
"dependencies": {
"defer-to-connect": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -1051,12 +1079,32 @@
"node": ">=10.13.0"
}
},
"node_modules/@types/cacheable-request": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
"integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/http-cache-semantics": "*",
"@types/keyv": "^3.1.4",
"@types/node": "*",
"@types/responselike": "^1.0.0"
}
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true
},
"node_modules/@types/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
"integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -1070,6 +1118,36 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/keyv": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "22.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.4.tgz",
"integrity": "sha512-99l6wv4HEzBQhvaU/UGoeBoCK61SCROQaCCGyQSgX2tEQ3rKkNZ2S7CEWnS/4s1LV+8ODdK21UeyR1fHP2mXug==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
"integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
@@ -1091,6 +1169,17 @@
"@types/webidl-conversions": "*"
}
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -1519,6 +1608,16 @@
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@@ -1541,9 +1640,9 @@
}
},
"node_modules/bullmq": {
"version": "5.34.5",
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.5.tgz",
"integrity": "sha512-MHho9EOhLCTY3ZF+dd0wHv0VlY2FtpBcopMRsvj0kPra4TAwBFh2pik/s4WbX56cIfCE+VzfHIHy4xvqp3g1+Q==",
"version": "5.34.6",
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.6.tgz",
"integrity": "sha512-pRCYyO9RlkQWxdmKlrNnUthyFwurYXRYLVXD1YIx+nCCdhAOiHatD8FDHbsT/w2I31c0NWoMcfZiIGuipiF7Lg==",
"license": "MIT",
"dependencies": {
"cron-parser": "^4.9.0",
@@ -1609,6 +1708,35 @@
}
}
},
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.6.0"
}
},
"node_modules/cacheable-request": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
"integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
"dev": true,
"license": "MIT",
"dependencies": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
@@ -1879,6 +2007,19 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
"integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-response": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@@ -2389,6 +2530,35 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/decompress-response/node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deep-eql": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
@@ -2405,6 +2575,16 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -3197,6 +3377,27 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"bin": {
"extract-zip": "cli.js"
},
"engines": {
"node": ">= 10.17.0"
},
"optionalDependencies": {
"@types/yauzl": "^2.9.1"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3215,6 +3416,16 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"pend": "~1.2.0"
}
},
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
@@ -3573,6 +3784,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@@ -3629,6 +3856,32 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/got": {
"version": "11.8.6",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=10.19.0"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
@@ -3709,6 +3962,14 @@
"node": ">=18.0.0"
}
},
"node_modules/hpagent": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz",
"integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -3782,6 +4043,13 @@
"entities": "^4.4.0"
}
},
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -3798,6 +4066,20 @@
"node": ">= 0.8"
}
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
},
"engines": {
"node": ">=10.19.0"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -3902,10 +4184,9 @@
"license": "ISC"
},
"node_modules/ioredis": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz",
"integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==",
"license": "MIT",
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.2.tgz",
"integrity": "sha512-0SZXGNGZ+WzISQ67QDyZ2x0+wVxjjUndtD8oSeik/4ajifeiRufed8fCb8QW8VMyi4MXcS+UO1k/0NGhvq1PAg==",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
@@ -4369,6 +4650,13 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@@ -4489,6 +4777,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -4626,6 +4924,16 @@
"node": ">= 0.6"
}
},
"node_modules/mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -5333,9 +5641,9 @@
}
},
"node_modules/mongoose": {
"version": "8.9.2",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.2.tgz",
"integrity": "sha512-mLWynmZS1v8HTeMxyLhskQncS1SkrjW1eLNuFDYGQMQ/5QrFrxTLNwWXeCRZeKT2lXyaxW8bnJC9AKPT9jYMkw==",
"version": "8.9.3",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.3.tgz",
"integrity": "sha512-G50GNPdMqhoiRAJ/24GYAzg13yxXDD3FOOFeYiFwtHmHpAJem3hxbYIxAhLJGWbYEiUZL0qFMu2LXYkgGAmo+Q==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.1",
@@ -5476,6 +5784,40 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"license": "MIT"
},
"node_modules/ngrok": {
"version": "5.0.0-beta.2",
"resolved": "https://registry.npmjs.org/ngrok/-/ngrok-5.0.0-beta.2.tgz",
"integrity": "sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ==",
"dev": true,
"hasInstallScript": true,
"license": "BSD-2-Clause",
"dependencies": {
"extract-zip": "^2.0.1",
"got": "^11.8.5",
"lodash.clonedeep": "^4.5.0",
"uuid": "^7.0.0 || ^8.0.0",
"yaml": "^2.2.2"
},
"bin": {
"ngrok": "bin/ngrok"
},
"engines": {
"node": ">=14.2"
},
"optionalDependencies": {
"hpagent": "^0.1.2"
}
},
"node_modules/ngrok/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/nise": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz",
@@ -5684,6 +6026,19 @@
"node": ">=0.10.0"
}
},
"node_modules/normalize-url": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
@@ -5777,6 +6132,16 @@
"node": ">= 0.8.0"
}
},
"node_modules/p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -5951,6 +6316,13 @@
"node": ">= 14.16"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -6646,6 +7018,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/rambda": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz",
@@ -6748,6 +7133,13 @@
"node": ">=0.10.0"
}
},
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
"dev": true,
"license": "MIT"
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -6757,6 +7149,19 @@
"node": ">=4"
}
},
"node_modules/responselike": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
"integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lowercase-keys": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -7702,6 +8107,13 @@
"node": ">=18.17"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
},
"node_modules/unfetch": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
@@ -8238,6 +8650,19 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
@@ -8322,6 +8747,17 @@
"node": ">=8"
}
},
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+3 -1
View File
@@ -18,13 +18,14 @@
"dependencies": {
"axios": "^1.7.2",
"bcrypt": "5.1.1",
"bullmq": "5.34.5",
"bullmq": "5.34.6",
"cors": "^2.8.5",
"dockerode": "4.0.2",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"handlebars": "^4.7.8",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
"joi": "^17.13.1",
"jsonwebtoken": "9.0.2",
"mailersend": "^2.2.0",
@@ -47,6 +48,7 @@
"esm": "3.2.25",
"globals": "^15.14.0",
"mocha": "11.0.1",
"ngrok": "5.0.0-beta.2",
"nodemon": "3.1.9",
"prettier": "^3.3.3",
"sinon": "19.0.2"
+18
View File
@@ -0,0 +1,18 @@
import { Router } from "express";
class DistributedUptimeRoutes {
constructor(distributedUptimeController) {
this.router = Router();
this.distributedUptimeController = distributedUptimeController;
this.initRoutes();
}
initRoutes() {
this.router.post("/callback", this.distributedUptimeController.resultsCallback);
}
getRouter() {
return this.router;
}
}
export default DistributedUptimeRoutes;
+8
View File
@@ -13,6 +13,14 @@ class MonitorRoutes {
this.router.get("/", this.monitorController.getAllMonitors);
this.router.get("/uptime", this.monitorController.getAllMonitorsWithUptimeStats);
this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById);
this.router.get(
"/hardware/details/:monitorId",
this.monitorController.getHardwareDetailsById
);
this.router.get(
"/uptime/details/:monitorId",
this.monitorController.getUptimeDetailsById
);
this.router.get("/certificate/:monitorId", (req, res, next) => {
this.monitorController.getMonitorCertificate(
req,
+325 -196
View File
@@ -1,30 +1,22 @@
const QUEUE_NAME = "monitors";
const JOBS_PER_WORKER = 5;
const QUEUE_NAMES = ["uptime", "pagespeed", "hardware"];
const SERVICE_NAME = "JobQueue";
const JOBS_PER_WORKER = 5;
const QUEUE_LOOKUP = {
hardware: "hardware",
http: "uptime",
ping: "uptime",
docker: "uptime",
pagespeed: "pagespeed",
};
import { errorMessages, successMessages } from "../utils/messages.js";
/**
* JobQueue
*
* This service is responsible for managing the job queue.
* It handles enqueuing, dequeuing, and processing jobs.
* It scales the number of workers based on the number of jobs/worker
*/
class JobQueue {
import { successMessages, errorMessages } from "../utils/messages.js";
class NewJobQueue {
static SERVICE_NAME = SERVICE_NAME;
/**
* @class JobQueue
* @classdesc Manages job queue and workers.
*
* @param {Object} statusService - Service for handling status updates.
* @param {Object} notificationService - Service for handling notifications.
* @param {Object} settingsService - Service for retrieving settings.
* @param {Object} logger - Logger for logging information.
* @param {Function} Queue - Queue constructor.
* @param {Function} Worker - Worker constructor.
*/
constructor(
db,
statusService,
networkService,
notificationService,
settingsService,
logger,
@@ -32,102 +24,71 @@ class JobQueue {
Worker
) {
const settings = settingsService.getSettings() || {};
const { redisHost = "127.0.0.1", redisPort = 6379 } = settings;
const connection = {
host: redisHost,
port: redisPort,
};
this.queue = new Queue(QUEUE_NAME, {
connection,
});
this.queues = {};
this.workers = {};
this.connection = connection;
this.workers = [];
this.db = null;
this.networkService = null;
this.db = db;
this.networkService = networkService;
this.statusService = statusService;
this.notificationService = notificationService;
this.settingsService = settingsService;
this.logger = logger;
this.Worker = Worker;
QUEUE_NAMES.forEach((name) => {
this.queues[name] = new Queue(name, { connection });
this.workers[name] = [];
});
}
/**
* Creates and initializes a JobQueue instance.
*
* @param {Object} db - Database service for accessing monitors.
* @param {Object} networkService - Service for network operations.
* @param {Object} statusService - Service for handling status updates.
* @param {Object} notificationService - Service for handling notifications.
* @param {Object} settingsService - Service for retrieving settings.
* @param {Object} logger - Logger for logging information.
* @param {Function} Queue - Queue constructor.
* @param {Function} Worker - Worker constructor.
* @returns {Promise<JobQueue>} - The initialized JobQueue instance.
* @throws {Error} - Throws an error if initialization fails.
* Initializes job queues by adding jobs for all active monitors
* @async
* @function initJobQueue
* @description Retrieves all monitors from the database and adds jobs for active ones to their respective queues
* @throws {Error} If there's an error retrieving monitors or adding jobs
* @returns {Promise<void>}
*/
static async createJobQueue(
db,
networkService,
statusService,
notificationService,
settingsService,
logger,
Queue,
Worker
) {
const queue = new JobQueue(
statusService,
notificationService,
settingsService,
logger,
Queue,
Worker
);
try {
queue.db = db;
queue.networkService = networkService;
const monitors = await db.getAllMonitors();
for (const monitor of monitors) {
if (monitor.isActive) {
queue.addJob(monitor.id, monitor).catch((error) => {
async initJobQueue() {
const monitors = await this.db.getAllMonitors();
await Promise.all(
monitors
.filter((monitor) => monitor.isActive)
.map(async (monitor) => {
try {
await this.addJob(monitor._id, monitor);
} catch (error) {
this.logger.error({
message: error.message,
message: `Failed to add job for monitor ${monitor._id}:`,
service: SERVICE_NAME,
method: "createJobQueue",
method: "initJobQueue",
stack: error.stack,
});
});
}
}
queue
.getWorkerStats()
.then((workerStats) => {
queue.scaleWorkers(workerStats);
}
})
.catch((error) => {
this.logger.error({
message: error.message,
service: SERVICE_NAME,
method: "createJobQueue",
stack: error.stack,
});
});
return queue;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "createJobQueue") : null;
throw error;
}
);
}
/**
* Checks if the given monitor is in a maintenance window.
*
* @param {string} monitorId - The ID of the monitor to check.
* @returns {Promise<boolean>} - Returns true if the monitor is in a maintenance window, otherwise false.
* @throws {Error} - Throws an error if the database query fails.
* Checks if a monitor is currently in a maintenance window
* @async
* @param {string} monitorId - The ID of the monitor to check
* @returns {Promise<boolean>} Returns true if the monitor is in an active maintenance window, false otherwise
* @throws {Error} If there's an error retrieving maintenance windows from the database
* @description
* Retrieves all maintenance windows for a monitor and checks if any are currently active.
* A maintenance window is considered active if:
* 1. The window is marked as active AND
* 2. Either:
* - Current time falls between start and end times
* - For repeating windows: Current time falls between any repeated interval
*/
async isInMaintenanceWindow(monitorId) {
const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(monitorId);
@@ -160,39 +121,70 @@ class JobQueue {
}
/**
* Creates a job handler function for processing jobs.
* Creates a job processing handler for monitor checks
* @function createJobHandler
* @returns {Function} An async function that processes monitor check jobs
* @description
* Creates and returns a job handler that:
* 1. Checks if monitor is in maintenance window
* 2. If not in maintenance, performs network status check
* 3. Updates monitor status in database
* 4. Triggers notifications if status changed
*
* @returns {Function} An async function that processes a job.
* @param {Object} job - The job to process
* @param {Object} job.data - The monitor data
* @param {string} job.data._id - Monitor ID
* @param {string} job.id - Job ID
*
* @throws {Error} Logs errors but doesn't throw them to prevent job failure
* @returns {Promise<void>} Resolves when job processing is complete
*/
createJobHandler() {
return async (job) => {
try {
// Get all maintenance windows for this monitor
await job.updateProgress(0);
const monitorId = job.data._id;
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId);
// If a maintenance window is active, we're done
if (maintenanceWindowActive) {
await job.updateProgress(100);
this.logger.info({
message: `Monitor ${monitorId} is in maintenance window`,
service: SERVICE_NAME,
method: "createWorker",
});
return;
return false;
}
// Get the current status
await job.updateProgress(30);
const networkResponse = await this.networkService.getStatus(job);
// Handle status change
await job.updateProgress(60);
const { monitor, statusChanged, prevStatus } =
await this.statusService.updateStatus(networkResponse);
// Handle notifications
this.notificationService.handleNotifications({
...networkResponse,
monitor,
prevStatus,
statusChanged,
});
await job.updateProgress(80);
this.notificationService
.handleNotifications({
...networkResponse,
monitor,
prevStatus,
statusChanged,
})
.catch((error) => {
this.logger.error({
message: error.message,
service: SERVICE_NAME,
method: "createJobHandler",
details: `Error sending notifications for job ${job.id}: ${error.message}`,
stack: error.stack,
});
});
await job.updateProgress(100);
return true;
} catch (error) {
this.logger.error({
message: error.message,
@@ -201,29 +193,41 @@ class JobQueue {
details: `Error processing job ${job.id}: ${error.message}`,
stack: error.stack,
});
throw error;
}
};
}
/**
* Creates a worker for the queue
* Operations are carried out in the async callback
* @returns {Worker} The newly created worker
* Creates a new worker for processing jobs in a queue
* @param {Queue} queue - The BullMQ queue to create a worker for
* @returns {Worker} A new BullMQ worker instance
* @description
* Creates and configures a new worker with:
* - Queue-specific job handler
* - Redis connection settings
* - Default worker options
* The worker processes jobs from the specified queue using the job handler
* created by createJobHandler()
*
* @throws {Error} If worker creation fails or connection is invalid
*/
createWorker() {
const worker = new this.Worker(QUEUE_NAME, this.createJobHandler(), {
createWorker(queue) {
const worker = new this.Worker(queue.name, this.createJobHandler(), {
connection: this.connection,
concurrency: 5,
});
worker.on("failed", (job, err) => {
this.logger.error({
message: `Worker failed job: ${job.id}`,
service: SERVICE_NAME,
method: "createWorker",
stack: err.stack,
});
});
return worker;
}
/**
* @typedef {Object} WorkerStats
* @property {Array<Job>} jobs - Array of jobs in the Queue
* @property {number} - workerLoad - The number of jobs per worker
*
*/
/**
* Gets stats related to the workers
* This is used for scaling workers right now
@@ -233,10 +237,10 @@ class JobQueue {
* @async
* @returns {Promise<WorkerStats>} - Returns the worker stats
*/
async getWorkerStats() {
async getWorkerStats(queue) {
try {
const jobs = await this.queue.getRepeatableJobs();
const load = jobs.length / this.workers.length;
const jobs = await queue.getRepeatableJobs();
const load = jobs.length / this.workers[queue.name].length;
return { jobs, load };
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
@@ -246,46 +250,56 @@ class JobQueue {
}
/**
* Scale Workers
* This function scales workers based on the load per worker
* If the load is higher than the JOBS_PER_WORKER threshold, we add more workers
* If the load is lower than the JOBS_PER_WORKER threshold, we release workers
* This approach ignores server performance, which we should add in the future
*
* Scales workers up or down based on queue load
* @async
* @param {Object} workerStats - Statistics about current worker load
* @param {number} workerStats.load - Current load per worker
* @param {Array} workerStats.jobs - Array of current jobs
* @param {Queue} queue - The BullMQ queue to scale workers for
* @returns {Promise<boolean>} True if scaling occurred, false if no scaling was needed
* @throws {Error} If no workers array exists for the queue
* @description
* Scales workers based on these rules:
* - Maintains minimum of 5 workers
* - Adds workers if load exceeds JOBS_PER_WORKER
* - Removes workers if load is below JOBS_PER_WORKER
* - Creates initial workers if none exist
* Worker scaling is calculated based on excess jobs or excess capacity
*/
async scaleWorkers(workerStats, queue) {
const workers = this.workers[queue.name];
if (workers === undefined) {
throw new Error(`No workers found for ${queue.name}`);
}
* @async
* @param {WorkerStats} workerStats - The payload for the job.
* @returns {Promise<boolean>}
*/
async scaleWorkers(workerStats) {
if (this.workers.length === 0) {
if (workers.length === 0) {
// There are no workers, need to add one
for (let i = 0; i < 5; i++) {
const worker = this.createWorker();
this.workers.push(worker);
const worker = this.createWorker(queue);
workers.push(worker);
}
return true;
}
if (workerStats.load > JOBS_PER_WORKER) {
// Find out how many more jobs we have than current workers can handle
const excessJobs = workerStats.jobs.length - this.workers.length * JOBS_PER_WORKER;
const excessJobs = workerStats.jobs.length - workers.length * JOBS_PER_WORKER;
// Divide by jobs/worker to find out how many workers to add
const workersToAdd = Math.ceil(excessJobs / JOBS_PER_WORKER);
for (let i = 0; i < workersToAdd; i++) {
const worker = this.createWorker();
this.workers.push(worker);
const worker = this.createWorker(queue);
workers.push(worker);
}
return true;
}
if (workerStats.load < JOBS_PER_WORKER) {
// Find out how much excess capacity we have
const workerCapacity = this.workers.length * JOBS_PER_WORKER;
const workerCapacity = workers.length * JOBS_PER_WORKER;
const excessCapacity = workerCapacity - workerStats.jobs.length;
// Calculate how many workers to remove
let workersToRemove = Math.floor(excessCapacity / JOBS_PER_WORKER); // Make sure there are always at least 5
while (workersToRemove > 0 && this.workers.length > 5) {
const worker = this.workers.pop();
while (workersToRemove > 0 && workers.length > 5) {
const worker = workers.pop();
workersToRemove--;
await worker.close().catch((error) => {
// Catch the error instead of throwing it
@@ -309,9 +323,9 @@ class JobQueue {
* @returns {Promise<Array<Job>>}
* @throws {Error} - Throws error if getting jobs fails
*/
async getJobs() {
async getJobs(queue) {
try {
const jobs = await this.queue.getRepeatableJobs();
const jobs = await queue.getRepeatableJobs();
return jobs;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
@@ -321,21 +335,32 @@ class JobQueue {
}
/**
* Retrieves the statistics of jobs and workers.
*
* @returns {Promise<Object>} - An object containing job statistics and the number of workers.
* @throws {Error} - Throws an error if the job statistics retrieval fails.
* Retrieves detailed statistics about jobs and workers for all queues
* @async
* @returns {Promise<Object>} Queue statistics object
* @throws {Error} If there's an error retrieving job information
* @description
* Returns an object with statistics for each queue including:
* - List of jobs with their URLs and current states
* - Number of workers assigned to the queue
*/
async getJobStats() {
try {
const jobs = await this.queue.getJobs();
const ret = await Promise.all(
jobs.map(async (job) => {
const state = await job.getState();
return { url: job.data.url, state };
let stats = {};
await Promise.all(
QUEUE_NAMES.map(async (name) => {
const queue = this.queues[name];
const jobs = await queue.getJobs();
const ret = await Promise.all(
jobs.map(async (job) => {
const state = await job.getState();
return { url: job.data.url, state };
})
);
stats[name] = { jobs: ret, workers: this.workers[name].length };
})
);
return { jobs: ret, workers: this.workers.length };
return stats;
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getJobStats") : null;
@@ -344,25 +369,63 @@ class JobQueue {
}
/**
* Adds a job to the queue and scales workers based on worker stats.
*
* Adds both immediate and repeatable jobs to the appropriate queue
* @async
* @param {string} jobName - The name of the job to be added.
* @param {Monitor} payload - The payload for the job.
* @throws {Error} - Will throw an error if the job cannot be added or workers don't scale
* @param {string} jobName - Name identifier for the job
* @param {Object} monitor - Job data and configuration
* @param {string} monitor.type - Type of monitor/queue ('uptime', 'pagespeed', 'hardware')
* @param {string} [monitor.url] - URL to monitor (optional)
* @param {number} [monitor.interval=60000] - Repeat interval in milliseconds
* @param {string} monitor._id - Monitor ID
* @throws {Error} If queue not found for payload type
* @throws {Error} If job addition fails
* @description
* 1. Identifies correct queue based on payload type
* 2. Adds immediate job execution
* 3. Adds repeatable job with specified interval
* 4. Scales workers based on updated queue load
* Jobs are configured with exponential backoff, single attempt,
* and automatic removal on completion
*/
async addJob(jobName, payload) {
async addJob(jobName, monitor) {
try {
this.logger.info({ message: `Adding job ${payload?.url ?? "No URL"}` });
this.logger.info({
message: `Adding job ${monitor?.url ?? "No URL"}`,
service: SERVICE_NAME,
method: "addJob",
});
// Find the correct queue
const queue = this.queues[QUEUE_LOOKUP[monitor.type]];
if (queue === undefined) {
throw new Error(`Queue for ${monitor.type} not found`);
}
// build job options
const jobOptions = {
attempts: 1,
backoff: {
type: "exponential",
delay: 1000,
},
removeOnComplete: true,
removeOnFail: false,
timeout: 1 * 60 * 1000,
};
// Execute job immediately
await this.queue.add(jobName, payload);
await this.queue.add(jobName, payload, {
await queue.add(jobName, monitor, jobOptions);
await queue.add(jobName, monitor, {
...jobOptions,
repeat: {
every: payload?.interval ?? 60000,
every: monitor?.interval ?? 60000,
immediately: false,
},
});
const workerStats = await this.getWorkerStats();
await this.scaleWorkers(workerStats);
const workerStats = await this.getWorkerStats(queue);
await this.scaleWorkers(workerStats, queue);
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "addJob") : null;
@@ -371,15 +434,25 @@ class JobQueue {
}
/**
* Deletes a job from the queue.
*
* Deletes a repeatable job from its queue and adjusts worker scaling
* @async
* @param {Monitor} monitor - The monitor to remove.
* @throws {Error}
* @param {Object} monitor - Monitor object containing job details
* @param {string} monitor._id - ID of the monitor/job to delete
* @param {string} monitor.type - Type of monitor determining queue selection
* @param {number} monitor.interval - Job repeat interval in milliseconds
* @throws {Error} If queue not found for monitor type
* @throws {Error} If job deletion fails
* @description
* 1. Identifies correct queue based on monitor type
* 2. Removes repeatable job using monitor ID and interval
* 3. Logs success or failure of deletion
* 4. Updates worker scaling based on new queue load
* Returns void but logs operation result
*/
async deleteJob(monitor) {
try {
const wasDeleted = await this.queue.removeRepeatable(monitor._id, {
const queue = this.queues[QUEUE_LOOKUP[monitor.type]];
const wasDeleted = await queue.removeRepeatable(monitor._id, {
every: monitor.interval,
});
if (wasDeleted === true) {
@@ -389,8 +462,8 @@ class JobQueue {
method: "deleteJob",
details: `Deleted job ${monitor._id}`,
});
const workerStats = await this.getWorkerStats();
await this.scaleWorkers(workerStats);
const workerStats = await this.getWorkerStats(queue);
await this.scaleWorkers(workerStats, queue);
} else {
this.logger.error({
message: errorMessages.JOB_QUEUE_DELETE_JOB,
@@ -407,21 +480,59 @@ class JobQueue {
}
/**
* Retrieves the metrics of the job queue.
* Retrieves comprehensive metrics for all queues
* @async
* @returns {Promise<Object.<string, QueueMetrics>>} Object with metrics for each queue
* @throws {Error} If metrics retrieval fails
* @description
* Collects the following metrics for each queue:
* - Number of waiting jobs
* - Number of active jobs
* - Number of completed jobs
* - Number of failed jobs
* - Number of delayed jobs
* - Number of repeatable jobs
* - Number of active workers
*
* @returns {Promise<Object>} - An object containing various job queue metrics.
* @throws {Error} - Throws an error if the metrics retrieval fails.
* @typedef {Object} QueueMetrics
* @property {number} waiting - Count of jobs waiting to be processed
* @property {number} active - Count of jobs currently being processed
* @property {number} completed - Count of successfully completed jobs
* @property {number} failed - Count of failed jobs
* @property {number} delayed - Count of delayed jobs
* @property {number} repeatableJobs - Count of repeatable job patterns
* @property {number} workers - Count of active workers for this queue
*/
async getMetrics() {
try {
const metrics = {
waiting: await this.queue.getWaitingCount(),
active: await this.queue.getActiveCount(),
completed: await this.queue.getCompletedCount(),
failed: await this.queue.getFailedCount(),
delayed: await this.queue.getDelayedCount(),
repeatableJobs: (await this.queue.getRepeatableJobs()).length,
};
let metrics = {};
await Promise.all(
QUEUE_NAMES.map(async (name) => {
const queue = this.queues[name];
const workers = this.workers[name];
const [waiting, active, completed, failed, delayed, repeatableJobs] =
await Promise.all([
queue.getWaitingCount(),
queue.getActiveCount(),
queue.getCompletedCount(),
queue.getFailedCount(),
queue.getDelayedCount(),
queue.getRepeatableJobs(),
]);
metrics[name] = {
waiting,
active,
completed,
failed,
delayed,
repeatableJobs: repeatableJobs.length,
workers: workers.length,
};
})
);
return metrics;
} catch (error) {
this.logger.error({
@@ -440,24 +551,42 @@ class JobQueue {
async obliterate() {
try {
this.logger.info({ message: "Attempting to obliterate job queue..." });
await this.queue.pause();
const jobs = await this.getJobs();
// Remove all repeatable jobs
for (const job of jobs) {
await this.queue.removeRepeatableByKey(job.key);
await this.queue.remove(job.id);
}
// Close workers
this.logger.info({
message: "Attempting to obliterate job queue...",
service: SERVICE_NAME,
method: "obliterate",
});
await Promise.all(
this.workers.map(async (worker) => {
await worker.close();
QUEUE_NAMES.map(async (name) => {
const queue = this.queues[name];
await queue.pause();
const jobs = await this.getJobs(queue);
// Remove all repeatable jobs
for (const job of jobs) {
await queue.removeRepeatableByKey(job.key);
await queue.remove(job.id);
}
})
);
await this.queue.obliterate();
// Close workers
await Promise.all(
QUEUE_NAMES.map(async (name) => {
const workers = this.workers[name];
await Promise.all(
workers.map(async (worker) => {
await worker.close();
})
);
})
);
QUEUE_NAMES.forEach(async (name) => {
const queue = this.queues[name];
await queue.obliterate();
});
const metrics = await this.getMetrics();
this.logger.info({
message: successMessages.JOB_QUEUE_OBLITERATE,
@@ -474,4 +603,4 @@ class JobQueue {
}
}
export default JobQueue;
export default NewJobQueue;
+2
View File
@@ -1,4 +1,6 @@
const SERVICE_NAME = "SettingsService";
import dotenv from "dotenv";
dotenv.config();
const envConfig = {
logLevel: undefined,
apiBaseUrl: undefined,
+96 -79
View File
@@ -18,14 +18,14 @@ import { getTokenFromHeaders, tokenType } from "../../utils/utils.js";
import logger from "../../utils/logger.js";
import e from "cors";
describe("Auth Controller - issueToken", () => {
describe("Auth Controller - issueToken", function() {
let stub;
afterEach(() => {
afterEach(function() {
sinon.restore(); // Restore stubs after each test
});
it("should reject with an error if jwt.sign fails", () => {
it("should reject with an error if jwt.sign fails", function() {
const error = new Error("jwt.sign error");
stub = sinon.stub(jwt, "sign").throws(error);
const payload = { id: "123" };
@@ -35,7 +35,7 @@ describe("Auth Controller - issueToken", () => {
);
});
it("should return a token if jwt.sign is successful and appSettings.jwtTTL is not defined", () => {
it("should return a token if jwt.sign is successful and appSettings.jwtTTL is not defined", function() {
const payload = { id: "123" };
const appSettings = { jwtSecret: "my_secret" };
const expectedToken = "mockToken";
@@ -45,7 +45,7 @@ describe("Auth Controller - issueToken", () => {
expect(token).to.equal(expectedToken);
});
it("should return a token if jwt.sign is successful and appSettings.jwtTTL is defined", () => {
it("should return a token if jwt.sign is successful and appSettings.jwtTTL is defined", function() {
const payload = { id: "123" };
const appSettings = { jwtSecret: "my_secret", jwtTTL: "1s" };
const expectedToken = "mockToken";
@@ -55,7 +55,7 @@ describe("Auth Controller - issueToken", () => {
expect(token).to.equal(expectedToken);
});
it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is not defined", () => {
it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is not defined", function() {
const payload = {};
const appSettings = { refreshTokenSecret: "my_refresh_secret" };
const expectedToken = "mockRefreshToken";
@@ -65,7 +65,7 @@ describe("Auth Controller - issueToken", () => {
expect(token).to.equal(expectedToken);
});
it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is defined", () => {
it("should return a refresh token if jwt.sign is successful and appSettings.refreshTokenTTL is defined", function() {
const payload = {};
const appSettings = {
refreshTokenSecret: "my_refresh_secret",
@@ -79,9 +79,10 @@ describe("Auth Controller - issueToken", () => {
});
});
describe("Auth Controller - registerUser", () => {
describe("Auth Controller - registerUser", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
body: {
firstName: "firstname",
@@ -116,25 +117,26 @@ describe("Auth Controller - registerUser", () => {
next = sinon.stub();
sinon.stub(logger, "error");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if body validation fails", async () => {
it("should reject with an error if body validation fails", async function() {
req.body = {};
await registerUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if checkSuperadmin fails", async () => {
it("should reject with an error if checkSuperadmin fails", async function() {
req.db.checkSuperadmin.throws(new Error("checkSuperadmin error"));
await registerUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error");
});
it("should reject with an error if getInviteTokenAndDelete fails", async () => {
it("should reject with an error if getInviteTokenAndDelete fails", async function() {
req.db.checkSuperadmin.returns(true);
req.db.getInviteTokenAndDelete.throws(new Error("getInviteTokenAndDelete error"));
await registerUser(req, res, next);
@@ -142,7 +144,7 @@ describe("Auth Controller - registerUser", () => {
expect(next.firstCall.args[0].message).to.equal("getInviteTokenAndDelete error");
});
it("should reject with an error if updateAppSettings fails", async () => {
it("should reject with an error if updateAppSettings fails", async function() {
req.db.checkSuperadmin.returns(false);
req.db.updateAppSettings.throws(new Error("updateAppSettings error"));
await registerUser(req, res, next);
@@ -150,7 +152,7 @@ describe("Auth Controller - registerUser", () => {
expect(next.firstCall.args[0].message).to.equal("updateAppSettings error");
});
it("should reject with an error if insertUser fails", async () => {
it("should reject with an error if insertUser fails", async function() {
req.db.checkSuperadmin.resolves(false);
req.db.updateAppSettings.resolves();
req.db.insertUser.rejects(new Error("insertUser error"));
@@ -159,7 +161,7 @@ describe("Auth Controller - registerUser", () => {
expect(next.firstCall.args[0].message).to.equal("insertUser error");
});
it("should reject with an error if settingsService.getSettings fails", async () => {
it("should reject with an error if settingsService.getSettings fails", async function() {
req.db.checkSuperadmin.resolves(false);
req.db.updateAppSettings.resolves();
req.db.insertUser.resolves({ _id: "123" });
@@ -171,7 +173,7 @@ describe("Auth Controller - registerUser", () => {
expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error");
});
it("should log an error if emailService.buildAndSendEmail fails", async () => {
it("should log an error if emailService.buildAndSendEmail fails", async function() {
req.db.checkSuperadmin.resolves(false);
req.db.updateAppSettings.resolves();
req.db.insertUser.returns({ _id: "123" });
@@ -184,7 +186,8 @@ describe("Auth Controller - registerUser", () => {
expect(logger.error.calledOnce).to.be.true;
expect(logger.error.firstCall.args[0].message).to.equal("emailService error");
});
it("should return a success message and data if all operations are successful", async () => {
it("should return a success message and data if all operations are successful", async function() {
const user = { _id: "123" };
req.db.checkSuperadmin.resolves(false);
req.db.updateAppSettings.resolves();
@@ -205,7 +208,8 @@ describe("Auth Controller - registerUser", () => {
).to.be.true;
expect(next.notCalled).to.be.true;
});
it("should return a success message and data if all operations are successful and superAdmin true", async () => {
it("should return a success message and data if all operations are successful and superAdmin true", async function() {
const user = { _id: "123" };
req.db.checkSuperadmin.resolves(true);
req.db.updateAppSettings.resolves();
@@ -228,9 +232,10 @@ describe("Auth Controller - registerUser", () => {
});
});
describe("Auth Controller - loginUser", () => {
describe("Auth Controller - loginUser", function() {
let req, res, next, user;
beforeEach(() => {
beforeEach(function() {
req = {
body: { email: "test@example.com", password: "Password123!" },
db: {
@@ -255,21 +260,22 @@ describe("Auth Controller - loginUser", () => {
comparePassword: sinon.stub(),
};
});
it("should reject with an error if validation fails", async () => {
it("should reject with an error if validation fails", async function() {
req.body = {};
await loginUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if getUserByEmail fails", async () => {
it("should reject with an error if getUserByEmail fails", async function() {
req.db.getUserByEmail.rejects(new Error("getUserByEmail error"));
await loginUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getUserByEmail error");
});
it("should login user successfully", async () => {
it("should login user successfully", async function() {
req.db.getUserByEmail.resolves(user);
user.comparePassword.resolves(true);
await loginUser(req, res, next);
@@ -290,7 +296,8 @@ describe("Auth Controller - loginUser", () => {
).to.be.true;
expect(next.notCalled).to.be.true;
});
it("should reject a user with an incorrect password", async () => {
it("should reject a user with an incorrect password", async function() {
req.body = {
email: "test@test.com",
password: "Password123!",
@@ -305,10 +312,10 @@ describe("Auth Controller - loginUser", () => {
});
});
describe("Auth Controller - refreshAuthToken", () => {
describe("Auth Controller - refreshAuthToken", function() {
let req, res, next, issueTokenStub;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {
"x-refresh-token": "valid_refresh_token",
@@ -332,11 +339,11 @@ describe("Auth Controller - refreshAuthToken", () => {
sinon.replace({ issueToken }, "issueToken", issueTokenStub);
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject if no refresh token is provided", async () => {
it("should reject if no refresh token is provided", async function() {
delete req.headers["x-refresh-token"];
await refreshAuthToken(req, res, next);
@@ -345,7 +352,7 @@ describe("Auth Controller - refreshAuthToken", () => {
expect(next.firstCall.args[0].status).to.equal(401);
});
it("should reject if the refresh token is invalid", async () => {
it("should reject if the refresh token is invalid", async function() {
jwt.verify.yields(new Error("invalid token"));
await refreshAuthToken(req, res, next);
@@ -354,7 +361,7 @@ describe("Auth Controller - refreshAuthToken", () => {
expect(next.firstCall.args[0].status).to.equal(401);
});
it("should reject if the refresh token is expired", async () => {
it("should reject if the refresh token is expired", async function() {
const error = new Error("Token expired");
error.name = "TokenExpiredError";
jwt.verify.yields(error);
@@ -364,7 +371,7 @@ describe("Auth Controller - refreshAuthToken", () => {
expect(next.firstCall.args[0].status).to.equal(401);
});
it("should reject if settingsService.getSettings fails", async () => {
it("should reject if settingsService.getSettings fails", async function() {
req.settingsService.getSettings.rejects(
new Error("settingsService.getSettings error")
);
@@ -374,7 +381,7 @@ describe("Auth Controller - refreshAuthToken", () => {
expect(next.firstCall.args[0].message).to.equal("settingsService.getSettings error");
});
it("should generate a new auth token if the refresh token is valid", async () => {
it("should generate a new auth token if the refresh token is valid", async function() {
const decodedPayload = { expiresIn: "60" };
jwt.verify.callsFake(() => {
return decodedPayload;
@@ -396,9 +403,10 @@ describe("Auth Controller - refreshAuthToken", () => {
});
});
describe("Auth Controller - editUser", async () => {
describe("Auth Controller - editUser", function() {
let req, res, next, stub, user;
beforeEach(() => {
beforeEach(function() {
req = {
params: { userId: "123" },
body: { password: "Password1!", newPassword: "Password2!" },
@@ -419,40 +427,41 @@ describe("Auth Controller - editUser", async () => {
next = sinon.stub();
stub = sinon.stub(jwt, "verify").returns({ email: "test@example.com" });
});
afterEach(() => {
afterEach(function() {
sinon.restore();
stub.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
req.params = {};
await editUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if body validation fails", async () => {
it("should reject with an error if body validation fails", async function() {
req.body = { invalid: 1 };
await editUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if param.userId !== req.user._id", async () => {
it("should reject with an error if param.userId !== req.user._id", async function() {
req.params = { userId: "456" };
await editUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(401);
});
it("should reject with an error if !req.body.password and getUserByEmail fails", async () => {
it("should reject with an error if !req.body.password and getUserByEmail fails", async function() {
req.db.getUserByEmail.rejects(new Error("getUserByEmail error"));
await editUser(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getUserByEmail error");
});
it("should reject with an error if user.comparePassword fails", async () => {
it("should reject with an error if user.comparePassword fails", async function() {
req.db.getUserByEmail.returns({
comparePassword: sinon.stub().rejects(new Error("Bad Password Match")),
});
@@ -461,7 +470,7 @@ describe("Auth Controller - editUser", async () => {
expect(next.firstCall.args[0].message).to.equal("Bad Password Match");
});
it("should reject with an error if user.comparePassword returns false", async () => {
it("should reject with an error if user.comparePassword returns false", async function() {
req.db.getUserByEmail.returns({
comparePassword: sinon.stub().returns(false),
});
@@ -473,7 +482,7 @@ describe("Auth Controller - editUser", async () => {
);
});
it("should edit a user if it receives a proper request", async () => {
it("should edit a user if it receives a proper request", async function() {
const user = {
comparePassword: sinon.stub().resolves(true),
};
@@ -495,7 +504,7 @@ describe("Auth Controller - editUser", async () => {
expect(next.notCalled).to.be.true;
});
it("should edit a user if it receives a proper request and both password fields are undefined", async () => {
it("should edit a user if it receives a proper request and both password fields are undefined", async function() {
req.body.password = undefined;
req.body.newPassword = undefined;
req.db.getUserByEmail.resolves(user);
@@ -513,7 +522,7 @@ describe("Auth Controller - editUser", async () => {
expect(next.notCalled).to.be.true;
});
it("should reject an edit request if password format is incorrect", async () => {
it("should reject an edit request if password format is incorrect", async function() {
req.body = { password: "bad_password", newPassword: "bad_password" };
const user = {
comparePassword: sinon.stub().resolves(true),
@@ -527,9 +536,10 @@ describe("Auth Controller - editUser", async () => {
});
});
describe("Auth Controller - checkSuperadminExists", async () => {
describe("Auth Controller - checkSuperadminExists", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
db: {
checkSuperadmin: sinon.stub(),
@@ -542,14 +552,14 @@ describe("Auth Controller - checkSuperadminExists", async () => {
next = sinon.stub();
});
it("should reject with an error if checkSuperadmin fails", async () => {
it("should reject with an error if checkSuperadmin fails", async function() {
req.db.checkSuperadmin.rejects(new Error("checkSuperadmin error"));
await checkSuperadminExists(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("checkSuperadmin error");
});
it("should return true if a superadmin exists", async () => {
it("should return true if a superadmin exists", async function() {
req.db.checkSuperadmin.resolves(true);
await checkSuperadminExists(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
@@ -563,7 +573,7 @@ describe("Auth Controller - checkSuperadminExists", async () => {
expect(next.notCalled).to.be.true;
});
it("should return false if a superadmin does not exist", async () => {
it("should return false if a superadmin does not exist", async function() {
req.db.checkSuperadmin.resolves(false);
await checkSuperadminExists(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
@@ -578,9 +588,10 @@ describe("Auth Controller - checkSuperadminExists", async () => {
});
});
describe("Auth Controller - requestRecovery", () => {
describe("Auth Controller - requestRecovery", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
body: { email: "test@test.com" },
db: {
@@ -601,21 +612,21 @@ describe("Auth Controller - requestRecovery", () => {
next = sinon.stub();
});
it("should reject with an error if validation fails", async () => {
it("should reject with an error if validation fails", async function() {
req.body = {};
await requestRecovery(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if getUserByEmail fails", async () => {
it("should reject with an error if getUserByEmail fails", async function() {
req.db.getUserByEmail.rejects(new Error("getUserByEmail error"));
await requestRecovery(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getUserByEmail error");
});
it("should throw an error if the user is not found", async () => {
it("should throw an error if the user is not found", async function() {
req.db.getUserByEmail.resolves(null);
await requestRecovery(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
@@ -624,13 +635,14 @@ describe("Auth Controller - requestRecovery", () => {
// );
});
it("should throw an error if the email is not provided", async () => {
it("should throw an error if the email is not provided", async function() {
req.body = {};
await requestRecovery(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should return a success message if the email is provided", async () => {
it("should return a success message if the email is provided", async function() {
const user = { firstName: "John" };
const recoveryToken = { token: "recovery-token" };
const msgId = "message-id";
@@ -664,9 +676,10 @@ describe("Auth Controller - requestRecovery", () => {
});
});
describe("Auth Controller - validateRecovery", () => {
describe("Auth Controller - validateRecovery", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
body: { recoveryToken: "recovery-token" },
db: {
@@ -680,21 +693,21 @@ describe("Auth Controller - validateRecovery", () => {
next = sinon.stub();
});
it("should reject with an error if validation fails", async () => {
it("should reject with an error if validation fails", async function() {
req.body = {};
await validateRecovery(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if validateRecoveryToken fails", async () => {
it("should reject with an error if validateRecoveryToken fails", async function() {
req.db.validateRecoveryToken.rejects(new Error("validateRecoveryToken error"));
await validateRecovery(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("validateRecoveryToken error");
});
it("should return a success message if the token is valid", async () => {
it("should return a success message if the token is valid", async function() {
req.db.validateRecoveryToken.resolves();
await validateRecovery(req, res, next);
expect(res.status.calledOnceWith(200)).to.be.true;
@@ -708,9 +721,10 @@ describe("Auth Controller - validateRecovery", () => {
});
});
describe("Auth Controller - resetPassword", () => {
describe("Auth Controller - resetPassword", function() {
let req, res, next, newPasswordValidation, handleValidationError, handleError;
beforeEach(() => {
beforeEach(function() {
req = {
body: {
recoveryToken: "recovery-token",
@@ -734,14 +748,15 @@ describe("Auth Controller - resetPassword", () => {
handleValidationError = sinon.stub();
handleError = sinon.stub();
});
it("should reject with an error if validation fails", async () => {
it("should reject with an error if validation fails", async function() {
req.body = { password: "bad_password" };
await resetPassword(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if resetPassword fails", async () => {
it("should reject with an error if resetPassword fails", async function() {
const error = new Error("resetPassword error");
newPasswordValidation.validateAsync.resolves();
req.db.resetPassword.rejects(error);
@@ -750,7 +765,7 @@ describe("Auth Controller - resetPassword", () => {
expect(next.firstCall.args[0].message).to.equal("resetPassword error");
});
it("should reset password successfully", async () => {
it("should reset password successfully", async function() {
const user = { _doc: {} };
const appSettings = { jwtSecret: "my_secret" };
const token = "token";
@@ -775,9 +790,10 @@ describe("Auth Controller - resetPassword", () => {
});
});
describe("Auth Controller - deleteUser", () => {
describe("Auth Controller - deleteUser", function() {
let req, res, next, handleError;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {
authorization: "Bearer token",
@@ -809,10 +825,11 @@ describe("Auth Controller - deleteUser", () => {
handleError = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should throw an error if user is not found", async () => {
it("should throw an error if user is not found", async function() {
jwt.decode.returns({ email: "test@example.com" });
req.db.getUserByEmail.throws(new Error(errorMessages.DB_USER_NOT_FOUND));
@@ -825,7 +842,7 @@ describe("Auth Controller - deleteUser", () => {
expect(res.json.notCalled).to.be.true;
});
it("should delete user and associated data if user is superadmin", async () => {
it("should delete user and associated data if user is superadmin", async function() {
const user = {
_id: "user_id",
email: "test@example.com",
@@ -865,7 +882,7 @@ describe("Auth Controller - deleteUser", () => {
expect(next.notCalled).to.be.true;
});
it("should delete user if user is not superadmin", async () => {
it("should delete user if user is not superadmin", async function() {
const user = {
_id: "user_id",
email: "test@example.com",
@@ -895,7 +912,7 @@ describe("Auth Controller - deleteUser", () => {
expect(next.notCalled).to.be.true;
});
it("should handle errors", async () => {
it("should handle errors", async function() {
const error = new Error("Something went wrong");
const SERVICE_NAME = "AuthController";
jwt.decode.returns({ email: "test@example.com" });
@@ -908,10 +925,10 @@ describe("Auth Controller - deleteUser", () => {
});
});
describe("Auth Controller - getAllUsers", () => {
describe("Auth Controller - getAllUsers", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
db: {
getAllUsers: sinon.stub(),
@@ -924,11 +941,11 @@ describe("Auth Controller - getAllUsers", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore(); // Restore the original methods after each test
});
it("should return 200 and all users", async () => {
it("should return 200 and all users", async function() {
const allUsers = [{ id: 1, name: "John Doe" }];
req.db.getAllUsers.resolves(allUsers);
@@ -946,7 +963,7 @@ describe("Auth Controller - getAllUsers", () => {
expect(next.notCalled).to.be.true;
});
it("should call next with error when an exception occurs", async () => {
it("should call next with error when an exception occurs", async function() {
const error = new Error("Something went wrong");
req.db.getAllUsers.rejects(error);
await getAllUsers(req, res, next);
@@ -9,9 +9,10 @@ import {
import jwt from "jsonwebtoken";
import { errorMessages, successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("Check Controller - createCheck", () => {
describe("Check Controller - createCheck", function() {
let req, res, next, handleError;
beforeEach(() => {
beforeEach(function() {
req = {
params: {},
body: {},
@@ -27,17 +28,17 @@ describe("Check Controller - createCheck", () => {
handleError = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore(); // Restore the original methods after each test
});
it("should reject with a validation if params are invalid", async () => {
it("should reject with a validation if params are invalid", async function() {
await createCheck(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with a validation error if body is invalid", async () => {
it("should reject with a validation error if body is invalid", async function() {
req.params = {
monitorId: "monitorId",
};
@@ -46,7 +47,7 @@ describe("Check Controller - createCheck", () => {
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should call next with error if data retrieval fails", async () => {
it("should call next with error if data retrieval fails", async function() {
req.params = {
monitorId: "monitorId",
};
@@ -62,7 +63,7 @@ describe("Check Controller - createCheck", () => {
expect(next.firstCall.args[0]).to.be.an("error");
});
it("should return a success message if check is created", async () => {
it("should return a success message if check is created", async function() {
req.params = {
monitorId: "monitorId",
};
@@ -87,9 +88,10 @@ describe("Check Controller - createCheck", () => {
});
});
describe("Check Controller - getChecks", () => {
describe("Check Controller - getChecks", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {},
query: {},
@@ -105,17 +107,17 @@ describe("Check Controller - getChecks", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with a validation error if params are invalid", async () => {
it("should reject with a validation error if params are invalid", async function() {
await getChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should return a success message if checks are found", async () => {
it("should return a success message if checks are found", async function() {
req.params = {
monitorId: "monitorId",
};
@@ -133,7 +135,7 @@ describe("Check Controller - getChecks", () => {
expect(next.notCalled).to.be.true;
});
it("should call next with error if data retrieval fails", async () => {
it("should call next with error if data retrieval fails", async function() {
req.params = {
monitorId: "monitorId",
};
@@ -143,9 +145,10 @@ describe("Check Controller - getChecks", () => {
});
});
describe("Check Controller - getTeamChecks", () => {
describe("Check Controller - getTeamChecks", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {},
query: {},
@@ -160,17 +163,17 @@ describe("Check Controller - getTeamChecks", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with a validation error if params are invalid", async () => {
it("should reject with a validation error if params are invalid", async function() {
await getTeamChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should return 200 and check data on successful validation and data retrieval", async () => {
it("should return 200 and check data on successful validation and data retrieval", async function() {
req.params = { teamId: "1" };
const checkData = [{ id: 1, name: "Check 1" }];
req.db.getTeamChecks.resolves(checkData);
@@ -187,7 +190,7 @@ describe("Check Controller - getTeamChecks", () => {
).to.be.true;
});
it("should call next with error if data retrieval fails", async () => {
it("should call next with error if data retrieval fails", async function() {
req.params = { teamId: "1" };
req.db.getTeamChecks.rejects(new Error("Retrieval Error"));
await getTeamChecks(req, res, next);
@@ -198,9 +201,10 @@ describe("Check Controller - getTeamChecks", () => {
});
});
describe("Check Controller - deleteChecks", () => {
describe("Check Controller - deleteChecks", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {},
db: {
@@ -214,17 +218,17 @@ describe("Check Controller - deleteChecks", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
await deleteChecks(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should call next with error if data retrieval fails", async () => {
it("should call next with error if data retrieval fails", async function() {
req.params = { monitorId: "1" };
req.db.deleteChecks.rejects(new Error("Deletion Error"));
await deleteChecks(req, res, next);
@@ -234,7 +238,7 @@ describe("Check Controller - deleteChecks", () => {
expect(res.json.notCalled).to.be.true;
});
it("should delete checks successfully", async () => {
it("should delete checks successfully", async function() {
req.params = { monitorId: "123" };
req.db.deleteChecks.resolves(1);
await deleteChecks(req, res, next);
@@ -250,9 +254,10 @@ describe("Check Controller - deleteChecks", () => {
});
});
describe("Check Controller - deleteChecksByTeamId", () => {
describe("Check Controller - deleteChecksByTeamId", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {},
db: {
@@ -266,17 +271,17 @@ describe("Check Controller - deleteChecksByTeamId", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
await deleteChecksByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should call next with error if data retrieval fails", async () => {
it("should call next with error if data retrieval fails", async function() {
req.params = { teamId: "1" };
req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error"));
await deleteChecksByTeamId(req, res, next);
@@ -286,7 +291,7 @@ describe("Check Controller - deleteChecksByTeamId", () => {
expect(res.json.notCalled).to.be.true;
});
it("should delete checks successfully", async () => {
it("should delete checks successfully", async function() {
req.params = { teamId: "123" };
req.db.deleteChecksByTeamId.resolves(1);
await deleteChecksByTeamId(req, res, next);
@@ -302,9 +307,10 @@ describe("Check Controller - deleteChecksByTeamId", () => {
});
});
describe("Check Controller - updateCheckTTL", () => {
describe("Check Controller - updateCheckTTL", function() {
let stub, req, res, next;
beforeEach(() => {
beforeEach(function() {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
@@ -326,18 +332,18 @@ describe("Check Controller - updateCheckTTL", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
stub.restore();
});
it("should reject if body validation fails", async () => {
it("should reject if body validation fails", async function() {
await updateChecksTTL(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should throw a JwtError if verification fails", async () => {
it("should throw a JwtError if verification fails", async function() {
stub.restore();
req.body = {
ttl: 1,
@@ -346,7 +352,7 @@ describe("Check Controller - updateCheckTTL", () => {
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
});
it("should call next with error if data retrieval fails", async () => {
it("should call next with error if data retrieval fails", async function() {
req.body = {
ttl: 1,
};
@@ -355,7 +361,7 @@ describe("Check Controller - updateCheckTTL", () => {
expect(next.firstCall.args[0]).to.be.an("error");
});
it("should update TTL successfully", async () => {
it("should update TTL successfully", async function() {
req.body = {
ttl: 1,
};
@@ -10,22 +10,22 @@ import sslChecker from "ssl-checker";
import { afterEach } from "node:test";
import exp from "constants";
describe("controllerUtils - handleValidationError", () => {
it("should set status to 422", () => {
describe("controllerUtils - handleValidationError", function() {
it("should set status to 422", function() {
const error = {};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.status).to.equal(422);
});
it("should set service to the provided serviceName", () => {
it("should set service to the provided serviceName", function() {
const error = {};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.service).to.equal(serviceName);
});
it("should set message to error.details[0].message if present", () => {
it("should set message to error.details[0].message if present", function() {
const error = {
details: [{ message: "Detail message" }],
};
@@ -34,7 +34,7 @@ describe("controllerUtils - handleValidationError", () => {
expect(result.message).to.equal("Detail message");
});
it("should set message to error.message if error.details is not present", () => {
it("should set message to error.message if error.details is not present", function() {
const error = {
message: "Error message",
};
@@ -43,7 +43,7 @@ describe("controllerUtils - handleValidationError", () => {
expect(result.message).to.equal("Error message");
});
it('should set message to "Validation Error" if neither error.details nor error.message is present', () => {
it('should set message to "Validation Error" if neither error.details nor error.message is present', function() {
const error = {};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
@@ -51,8 +51,8 @@ describe("controllerUtils - handleValidationError", () => {
});
});
describe("controllerUtils - handleError", () => {
it("should set stats to the provided status if error.code is undefined", () => {
describe("controllerUtils - handleError", function() {
it("should set stats to the provided status if error.code is undefined", function() {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
@@ -61,7 +61,7 @@ describe("controllerUtils - handleError", () => {
expect(result.status).to.equal(status);
});
it("should not overwrite error.code if it is already defined", () => {
it("should not overwrite error.code if it is already defined", function() {
const error = { status: 404 };
const serviceName = "TestService";
const method = "testMethod";
@@ -70,7 +70,7 @@ describe("controllerUtils - handleError", () => {
expect(result.status).to.equal(404);
});
it("should set service to the provided serviceName if error.service is undefined", () => {
it("should set service to the provided serviceName if error.service is undefined", function() {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
@@ -78,7 +78,7 @@ describe("controllerUtils - handleError", () => {
expect(result.service).to.equal(serviceName);
});
it("should not overwrite error.service if it is already defined", () => {
it("should not overwrite error.service if it is already defined", function() {
const error = { service: "ExistingService" };
const serviceName = "TestService";
const method = "testMethod";
@@ -86,7 +86,7 @@ describe("controllerUtils - handleError", () => {
expect(result.service).to.equal("ExistingService");
});
it("should set method to the provided method if error.method is undefined", () => {
it("should set method to the provided method if error.method is undefined", function() {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
@@ -94,7 +94,7 @@ describe("controllerUtils - handleError", () => {
expect(result.method).to.equal(method);
});
it("should not overwrite error.method if it is already defined", () => {
it("should not overwrite error.method if it is already defined", function() {
const error = { method: "existingMethod" };
const serviceName = "TestService";
const method = "testMethod";
@@ -102,7 +102,7 @@ describe("controllerUtils - handleError", () => {
expect(result.method).to.equal("existingMethod");
});
it("should set code to 500 if error.code is undefined and no code is provided", () => {
it("should set code to 500 if error.code is undefined and no code is provided", function() {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
@@ -111,9 +111,10 @@ describe("controllerUtils - handleError", () => {
});
});
describe("controllerUtils - fetchMonitorCertificate", () => {
describe("controllerUtils - fetchMonitorCertificate", function() {
let sslChecker, monitor;
beforeEach(() => {
beforeEach(function() {
monitor = {
url: "https://www.google.com",
};
@@ -124,7 +125,7 @@ describe("controllerUtils - fetchMonitorCertificate", () => {
sinon.restore();
});
it("should reject with an error if a URL does not parse", async () => {
it("should reject with an error if a URL does not parse", async function() {
monitor.url = "invalidurl";
try {
await fetchMonitorCertificate(sslChecker, monitor);
@@ -134,7 +135,7 @@ describe("controllerUtils - fetchMonitorCertificate", () => {
}
});
it("should reject with an error if sslChecker throws an error", async () => {
it("should reject with an error if sslChecker throws an error", async function() {
sslChecker.rejects(new Error("Test error"));
try {
await fetchMonitorCertificate(sslChecker, monitor);
@@ -143,12 +144,14 @@ describe("controllerUtils - fetchMonitorCertificate", () => {
expect(error.message).to.equal("Test error");
}
});
it("should return a certificate if sslChecker resolves", async () => {
it("should return a certificate if sslChecker resolves", async function() {
sslChecker.resolves({ validTo: "2022-01-01" });
const result = await fetchMonitorCertificate(sslChecker, monitor);
expect(result).to.deep.equal({ validTo: "2022-01-01" });
});
it("should throw an error if a ssl-checker returns null", async () => {
it("should throw an error if a ssl-checker returns null", async function() {
sslChecker.returns(null);
await fetchMonitorCertificate(sslChecker, monitor).catch((error) => {
expect(error).to.be.an("error");
@@ -5,9 +5,10 @@ import {
import jwt from "jsonwebtoken";
import sinon from "sinon";
import joi from "joi";
describe("inviteController - issueInvitation", () => {
describe("inviteController - issueInvitation", function() {
let req, res, next, stub;
beforeEach(() => {
beforeEach(function() {
req = {
headers: { authorization: "Bearer token" },
body: {
@@ -26,11 +27,11 @@ describe("inviteController - issueInvitation", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if role validation fails", async () => {
it("should reject with an error if role validation fails", async function() {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["bad_role"], firstname: "first_name", teamId: "1" };
});
@@ -41,7 +42,7 @@ describe("inviteController - issueInvitation", () => {
stub.restore();
});
it("should reject with an error if body validation fails", async () => {
it("should reject with an error if body validation fails", async function() {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["admin"], firstname: "first_name", teamId: "1" };
});
@@ -52,7 +53,7 @@ describe("inviteController - issueInvitation", () => {
stub.restore();
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
stub = sinon.stub(jwt, "decode").callsFake(() => {
return { role: ["admin"], firstname: "first_name", teamId: "1" };
});
@@ -63,7 +64,7 @@ describe("inviteController - issueInvitation", () => {
stub.restore();
});
it("should send an invite successfully", async () => {
it("should send an invite successfully", async function() {
const token = "token";
const decodedToken = {
role: "admin",
@@ -91,7 +92,7 @@ describe("inviteController - issueInvitation", () => {
stub.restore();
});
it("should send an email successfully", async () => {
it("should send an email successfully", async function() {
const token = "token";
const decodedToken = {
role: "admin",
@@ -124,7 +125,7 @@ describe("inviteController - issueInvitation", () => {
stub.restore();
});
it("should continue executing if sending an email fails", async () => {
it("should continue executing if sending an email fails", async function() {
const token = "token";
req.emailService.buildAndSendEmail.rejects(new Error("Email error"));
const decodedToken = {
@@ -153,9 +154,10 @@ describe("inviteController - issueInvitation", () => {
});
});
describe("inviteController - inviteVerifyController", () => {
describe("inviteController - inviteVerifyController", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
body: { token: "token" },
db: {
@@ -169,25 +171,25 @@ describe("inviteController - inviteVerifyController", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if body validation fails", async () => {
it("should reject with an error if body validation fails", async function() {
req.body = {};
await inviteVerifyController(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.getInviteToken.throws(new Error("DB error"));
await inviteVerifyController(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return 200 and invite data when validation and invite retrieval are successful", async () => {
it("should return 200 and invite data when validation and invite retrieval are successful", async function() {
req.db.getInviteToken.resolves({ invite: "data" });
await inviteVerifyController(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
@@ -11,9 +11,10 @@ import jwt from "jsonwebtoken";
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("maintenanceWindowController - createMaintenanceWindows", () => {
describe("maintenanceWindowController - createMaintenanceWindows", function() {
let req, res, next, stub;
beforeEach(() => {
beforeEach(function() {
req = {
body: {
monitors: ["66ff52e7c5911c61698ac724"],
@@ -40,11 +41,11 @@ describe("maintenanceWindowController - createMaintenanceWindows", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if body validation fails", async () => {
it("should reject with an error if body validation fails", async function() {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
@@ -55,14 +56,14 @@ describe("maintenanceWindowController - createMaintenanceWindows", () => {
stub.restore();
});
it("should reject with an error if jwt.verify fails", async () => {
it("should reject with an error if jwt.verify fails", async function() {
stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError());
await createMaintenanceWindows(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
stub.restore();
});
it("should reject with an error DB operations fail", async () => {
it("should reject with an error DB operations fail", async function() {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
@@ -72,7 +73,8 @@ describe("maintenanceWindowController - createMaintenanceWindows", () => {
expect(next.firstCall.args[0].message).to.equal("DB error");
stub.restore();
});
it("should return success message if all operations are successful", async () => {
it("should return success message if all operations are successful", async function() {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
@@ -86,7 +88,8 @@ describe("maintenanceWindowController - createMaintenanceWindows", () => {
).to.be.true;
stub.restore();
});
it("should return success message if all operations are successful with active set to undefined", async () => {
it("should return success message if all operations are successful with active set to undefined", async function() {
req.body.active = undefined;
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
@@ -103,9 +106,10 @@ describe("maintenanceWindowController - createMaintenanceWindows", () => {
});
});
describe("maintenanceWindowController - getMaintenanceWindowById", () => {
describe("maintenanceWindowController - getMaintenanceWindowById", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
body: {},
params: {
@@ -128,20 +132,21 @@ describe("maintenanceWindowController - getMaintenanceWindowById", () => {
next = sinon.stub();
});
it("should reject if param validation fails", async () => {
it("should reject if param validation fails", async function() {
req.params = {};
await getMaintenanceWindowById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if DB operations fail", async () => {
it("should reject if DB operations fail", async function() {
req.db.getMaintenanceWindowById.throws(new Error("DB error"));
await getMaintenanceWindowById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message with data if all operations are successful", async () => {
it("should return success message with data if all operations are successful", async function() {
req.db.getMaintenanceWindowById.returns({ id: "123" });
await getMaintenanceWindowById(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
@@ -155,9 +160,10 @@ describe("maintenanceWindowController - getMaintenanceWindowById", () => {
});
});
describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", function() {
let req, res, next, stub;
beforeEach(() => {
beforeEach(function() {
req = {
body: {},
params: {},
@@ -178,7 +184,8 @@ describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
};
next = sinon.stub();
});
it("should reject if query validation fails", async () => {
it("should reject if query validation fails", async function() {
req.query = {
invalid: 1,
};
@@ -186,14 +193,15 @@ describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if jwt.verify fails", async () => {
it("should reject if jwt.verify fails", async function() {
stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError());
await getMaintenanceWindowsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
stub.restore();
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
@@ -204,7 +212,7 @@ describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
stub.restore();
});
it("should return success message with data if all operations are successful", async () => {
it("should return success message with data if all operations are successful", async function() {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
@@ -222,9 +230,10 @@ describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
});
});
describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () => {
describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
body: {},
params: {
@@ -248,25 +257,25 @@ describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () =>
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject if param validation fails", async () => {
it("should reject if param validation fails", async function() {
req.params = {};
await getMaintenanceWindowsByMonitorId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.getMaintenanceWindowsByMonitorId.throws(new Error("DB error"));
await getMaintenanceWindowsByMonitorId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message with data if all operations are successful", async () => {
it("should return success message with data if all operations are successful", async function() {
const data = [{ monitorId: "123" }];
req.db.getMaintenanceWindowsByMonitorId.returns(data);
await getMaintenanceWindowsByMonitorId(req, res, next);
@@ -282,9 +291,10 @@ describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () =>
});
});
describe("maintenanceWindowController - deleteMaintenanceWindow", () => {
describe("maintenanceWindowController - deleteMaintenanceWindow", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
body: {},
params: {
@@ -307,25 +317,26 @@ describe("maintenanceWindowController - deleteMaintenanceWindow", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject if param validation fails", async () => {
it("should reject if param validation fails", async function() {
req.params = {};
await deleteMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.deleteMaintenanceWindowById.throws(new Error("DB error"));
await deleteMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message if all operations are successful", async () => {
it("should return success message if all operations are successful", async function() {
await deleteMaintenanceWindow(req, res, next);
expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id));
expect(res.status.firstCall.args[0]).to.equal(200);
@@ -338,9 +349,10 @@ describe("maintenanceWindowController - deleteMaintenanceWindow", () => {
});
});
describe("maintenanceWindowController - editMaintenanceWindow", () => {
describe("maintenanceWindowController - editMaintenanceWindow", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
body: {
active: true,
@@ -367,32 +379,32 @@ describe("maintenanceWindowController - editMaintenanceWindow", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject if param validation fails", async () => {
it("should reject if param validation fails", async function() {
req.params = {};
await editMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject if body validation fails", async () => {
it("should reject if body validation fails", async function() {
req.body = { invalid: 1 };
await editMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.editMaintenanceWindowById.throws(new Error("DB error"));
await editMaintenanceWindow(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message with data if all operations are successful", async () => {
it("should return success message with data if all operations are successful", async function() {
const data = { id: "123" };
req.db.editMaintenanceWindowById.returns(data);
+201 -112
View File
@@ -21,9 +21,10 @@ import logger from "../../utils/logger.js";
import axios from "axios";
const SERVICE_NAME = "monitorController";
describe("Monitor Controller - getAllMonitors", () => {
describe("Monitor Controller - getAllMonitors", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {},
query: {},
@@ -38,17 +39,19 @@ describe("Monitor Controller - getAllMonitors", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.getAllMonitors.throws(new Error("DB error"));
await getAllMonitors(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message and data if all operations succeed", async () => {
it("should return success message and data if all operations succeed", async function() {
const data = [{ monitor: "data" }];
req.db.getAllMonitors.returns(data);
await getAllMonitors(req, res, next);
@@ -62,9 +65,10 @@ describe("Monitor Controller - getAllMonitors", () => {
).to.be.true;
});
});
describe("Monitor Controller - getAllMonitorsWithUptimeStats", () => {
describe("Monitor Controller - getAllMonitorsWithUptimeStats", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {},
query: {},
@@ -79,17 +83,19 @@ describe("Monitor Controller - getAllMonitorsWithUptimeStats", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.getAllMonitorsWithUptimeStats.throws(new Error("DB error"));
await getAllMonitorsWithUptimeStats(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message and data if all operations succeed", async () => {
it("should return success message and data if all operations succeed", async function() {
const data = [{ monitor: "data" }];
req.db.getAllMonitorsWithUptimeStats.returns(data);
await getAllMonitorsWithUptimeStats(req, res, next);
@@ -104,9 +110,10 @@ describe("Monitor Controller - getAllMonitorsWithUptimeStats", () => {
});
});
describe("Monitor Controller - getMonitorStatsById", () => {
describe("Monitor Controller - getMonitorStatsById", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {
monitorId: "123",
@@ -123,29 +130,33 @@ describe("Monitor Controller - getMonitorStatsById", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
req.params = {};
await getMonitorStatsById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if query validation fails", async () => {
it("should reject with an error if query validation fails", async function() {
req.query = { invalid: 1 };
await getMonitorStatsById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.getMonitorStatsById.throws(new Error("DB error"));
await getMonitorStatsById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message and data if all operations succeed", async () => {
it("should return success message and data if all operations succeed", async function() {
const data = [{ monitorStats: "data" }];
req.db.getMonitorStatsById.returns(data);
await getMonitorStatsById(req, res, next);
@@ -160,9 +171,10 @@ describe("Monitor Controller - getMonitorStatsById", () => {
});
});
describe("Monitor Controller - getMonitorCertificate", () => {
describe("Monitor Controller - getMonitorCertificate", function() {
let req, res, next, fetchMonitorCertificate;
beforeEach(() => {
beforeEach(function() {
req = {
params: {
monitorId: "123",
@@ -181,23 +193,25 @@ describe("Monitor Controller - getMonitorCertificate", () => {
fetchMonitorCertificate = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
req.params = {};
await getMonitorCertificate(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if getMonitorById operation fails", async () => {
it("should reject with an error if getMonitorById operation fails", async function() {
req.db.getMonitorById.throws(new Error("DB error"));
await getMonitorCertificate(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message and data if all operations succeed with a valid cert", async () => {
it("should return success message and data if all operations succeed with a valid cert", async function() {
req.db.getMonitorById.returns({ url: "https://www.google.com" });
const data = { certificate: "cert", validTo: "2024/08/08" };
fetchMonitorCertificate.returns(data);
@@ -211,7 +225,8 @@ describe("Monitor Controller - getMonitorCertificate", () => {
})
).to.be.true;
});
it("should return an error if fetchMonitorCertificate fails", async () => {
it("should return an error if fetchMonitorCertificate fails", async function() {
req.db.getMonitorById.returns({ url: "https://www.google.com" });
fetchMonitorCertificate.throws(new Error("Certificate error"));
await getMonitorCertificate(req, res, next, fetchMonitorCertificate);
@@ -220,9 +235,10 @@ describe("Monitor Controller - getMonitorCertificate", () => {
});
});
describe("Monitor Controller - getMonitorById", () => {
describe("Monitor Controller - getMonitorById", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {
monitorId: "123",
@@ -239,28 +255,33 @@ describe("Monitor Controller - getMonitorById", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
req.params = {};
await getMonitorById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if query param validation fails", async () => {
it("should reject with an error if query param validation fails", async function() {
req.query = { invalid: 1 };
await getMonitorById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.getMonitorById.throws(new Error("DB error"));
await getMonitorById(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return 404 if a monitor is not found", async () => {
it("should return 404 if a monitor is not found", async function() {
const error = new Error("Monitor not found");
error.status = 404;
req.db.getMonitorById.throws(error);
@@ -268,7 +289,8 @@ describe("Monitor Controller - getMonitorById", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(404);
});
it("should return success message and data if all operations succeed", async () => {
it("should return success message and data if all operations succeed", async function() {
const data = { monitor: "data" };
req.db.getMonitorById.returns(data);
await getMonitorById(req, res, next);
@@ -283,9 +305,10 @@ describe("Monitor Controller - getMonitorById", () => {
});
});
describe("Monitor Controller - getMonitorsAndSummaryByTeamId", () => {
describe("Monitor Controller - getMonitorsAndSummaryByTeamId", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {
teamId: "123",
@@ -302,29 +325,33 @@ describe("Monitor Controller - getMonitorsAndSummaryByTeamId", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
req.params = {};
await getMonitorsAndSummaryByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if query validation fails", async () => {
it("should reject with an error if query validation fails", async function() {
req.query = { invalid: 1 };
await getMonitorsAndSummaryByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.getMonitorsAndSummaryByTeamId.throws(new Error("DB error"));
await getMonitorsAndSummaryByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message and data if all operations succeed", async () => {
it("should return success message and data if all operations succeed", async function() {
const data = { monitors: "data", summary: "data" };
req.db.getMonitorsAndSummaryByTeamId.returns(data);
await getMonitorsAndSummaryByTeamId(req, res, next);
@@ -339,9 +366,10 @@ describe("Monitor Controller - getMonitorsAndSummaryByTeamId", () => {
});
});
describe("Monitor Controller - getMonitorsByTeamId", () => {
describe("Monitor Controller - getMonitorsByTeamId", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {
teamId: "123",
@@ -358,28 +386,33 @@ describe("Monitor Controller - getMonitorsByTeamId", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
req.params = {};
await getMonitorsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if query validation fails", async () => {
it("should reject with an error if query validation fails", async function() {
req.query = { invalid: 1 };
await getMonitorsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB operations fail", async () => {
it("should reject with an error if DB operations fail", async function() {
req.db.getMonitorsByTeamId.throws(new Error("DB error"));
await getMonitorsByTeamId(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should return success message and data if all operations succeed", async () => {
it("should return success message and data if all operations succeed", async function() {
const data = { monitors: "data" };
req.db.getMonitorsByTeamId.returns(data);
await getMonitorsByTeamId(req, res, next);
@@ -394,9 +427,10 @@ describe("Monitor Controller - getMonitorsByTeamId", () => {
});
});
describe("Monitor Controller - createMonitor", () => {
describe("Monitor Controller - createMonitor", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {},
query: {},
@@ -423,29 +457,34 @@ describe("Monitor Controller - createMonitor", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if body validation fails", async () => {
it("should reject with an error if body validation fails", async function() {
req.body = {};
await createMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB createMonitor operation fail", async () => {
it("should reject with an error if DB createMonitor operation fail", async function() {
req.db.createMonitor.throws(new Error("DB error"));
await createMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if DB createNotification operation fail", async () => {
it("should reject with an error if DB createNotification operation fail", async function() {
req.db.createNotification.throws(new Error("DB error"));
req.db.createMonitor.returns({ _id: "123" });
await createMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if monitor.save operation fail", async () => {
it("should reject with an error if monitor.save operation fail", async function() {
req.db.createMonitor.returns({
_id: "123",
save: sinon.stub().throws(new Error("Monitor save error")),
@@ -454,14 +493,16 @@ describe("Monitor Controller - createMonitor", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("Monitor save error");
});
it("should throw an error if addJob operation fails", async () => {
it("should throw an error if addJob operation fails", async function() {
req.db.createMonitor.returns({ _id: "123", save: sinon.stub() });
req.jobQueue.addJob.throws(new Error("Job error"));
await createMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("Job error");
});
it("should return success message and data if all operations succeed", async () => {
it("should return success message and data if all operations succeed", async function() {
const monitor = { _id: "123", save: sinon.stub() };
req.db.createMonitor.returns(monitor);
await createMonitor(req, res, next);
@@ -476,18 +517,21 @@ describe("Monitor Controller - createMonitor", () => {
});
});
describe("Monitor Controller - checkEndpointResolution", () => {
describe("Monitor Controller - checkEndpointResolution", function() {
let req, res, next, axiosGetStub;
beforeEach(() => {
beforeEach(function() {
req = { query: { monitorURL: "https://example.com" } };
res = { status: sinon.stub().returnsThis(), json: sinon.stub() };
next = sinon.stub();
axiosGetStub = sinon.stub(axios, "get");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should resolve the URL successfully", async () => {
it("should resolve the URL successfully", async function() {
axiosGetStub.resolves({ status: 200, statusText: "OK" });
await checkEndpointResolution(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
@@ -501,7 +545,8 @@ describe("Monitor Controller - checkEndpointResolution", () => {
).to.be.true;
expect(next.called).to.be.false;
});
it("should return an error if endpoint resolution fails", async () => {
it("should return an error if endpoint resolution fails", async function() {
const axiosError = new Error("resolution failed");
axiosError.code = "ENOTFOUND";
axiosGetStub.rejects(axiosError);
@@ -513,7 +558,8 @@ describe("Monitor Controller - checkEndpointResolution", () => {
expect(errorPassedToNext.code).to.equal("ENOTFOUND");
expect(errorPassedToNext.status).to.equal(500);
});
it("should reject with an error if query validation fails", async () => {
it("should reject with an error if query validation fails", async function() {
req.query.monitorURL = "invalid-url";
await checkEndpointResolution(req, res, next);
expect(next.calledOnce).to.be.true;
@@ -524,9 +570,10 @@ describe("Monitor Controller - checkEndpointResolution", () => {
});
});
describe("Monitor Controller - deleteMonitor", () => {
describe("Monitor Controller - deleteMonitor", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {
monitorId: "123",
@@ -550,22 +597,26 @@ describe("Monitor Controller - deleteMonitor", () => {
next = sinon.stub();
sinon.stub(logger, "error");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
req.params = {};
await deleteMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if DB deleteMonitor operation fail", async () => {
it("should reject with an error if DB deleteMonitor operation fail", async function() {
req.db.deleteMonitor.throws(new Error("DB error"));
await deleteMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should log an error if deleteJob throws an error", async () => {
it("should log an error if deleteJob throws an error", async function() {
const error = new Error("Job error");
const monitor = { name: "test_monitor", _id: "123" };
req.db.deleteMonitor.returns(monitor);
@@ -576,7 +627,8 @@ describe("Monitor Controller - deleteMonitor", () => {
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
);
});
it("should log an error if deleteChecks throws an error", async () => {
it("should log an error if deleteChecks throws an error", async function() {
const error = new Error("Checks error");
const monitor = { name: "test_monitor", _id: "123" };
req.db.deleteMonitor.returns(monitor);
@@ -587,7 +639,8 @@ describe("Monitor Controller - deleteMonitor", () => {
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
);
});
it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async () => {
it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function() {
const error = new Error("PageSpeed error");
const monitor = { name: "test_monitor", _id: "123" };
req.db.deleteMonitor.returns(monitor);
@@ -598,7 +651,8 @@ describe("Monitor Controller - deleteMonitor", () => {
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
);
});
it("should log an error if deleteNotificationsByMonitorId throws an error", async () => {
it("should log an error if deleteNotificationsByMonitorId throws an error", async function() {
const error = new Error("Notifications error");
const monitor = { name: "test_monitor", _id: "123" };
req.db.deleteMonitor.returns(monitor);
@@ -609,7 +663,8 @@ describe("Monitor Controller - deleteMonitor", () => {
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
);
});
it("should return success message if all operations succeed", async () => {
it("should return success message if all operations succeed", async function() {
const monitor = { name: "test_monitor", _id: "123" };
req.db.deleteMonitor.returns(monitor);
await deleteMonitor(req, res, next);
@@ -623,9 +678,10 @@ describe("Monitor Controller - deleteMonitor", () => {
});
});
describe("Monitor Controller - deleteAllMonitors", () => {
describe("Monitor Controller - deleteAllMonitors", function() {
let req, res, next, stub;
beforeEach(() => {
beforeEach(function() {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };
});
@@ -658,24 +714,28 @@ describe("Monitor Controller - deleteAllMonitors", () => {
next = sinon.stub();
sinon.stub(logger, "error");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
stub.restore();
});
it("should reject with an error if getTokenFromHeaders throws an error", async () => {
it("should reject with an error if getTokenFromHeaders throws an error", async function() {
req.headers = {};
await deleteAllMonitors(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("No auth headers");
expect(next.firstCall.args[0].status).to.equal(500);
});
it("should reject with an error if token validation fails", async () => {
it("should reject with an error if token validation fails", async function() {
stub.restore();
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
await deleteAllMonitors(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
});
it("should reject with an error if DB deleteAllMonitors operation fail", async () => {
it("should reject with an error if DB deleteAllMonitors operation fail", async function() {
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.db.deleteAllMonitors.throws(new Error("DB error"));
await deleteAllMonitors(req, res, next);
@@ -683,7 +743,7 @@ describe("Monitor Controller - deleteAllMonitors", () => {
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should log an error if deleteChecks throws an error", async () => {
it("should log an error if deleteChecks throws an error", async function() {
const monitors = [{ name: "test_monitor", _id: "123" }];
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
@@ -695,7 +755,8 @@ describe("Monitor Controller - deleteAllMonitors", () => {
`Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
);
});
it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async () => {
it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async function() {
const monitors = [{ name: "test_monitor", _id: "123" }];
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
@@ -707,7 +768,8 @@ describe("Monitor Controller - deleteAllMonitors", () => {
`Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
);
});
it("should log an error if deleteNotificationsByMonitorId throws an error", async () => {
it("should log an error if deleteNotificationsByMonitorId throws an error", async function() {
const monitors = [{ name: "test_monitor", _id: "123" }];
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
@@ -719,7 +781,8 @@ describe("Monitor Controller - deleteAllMonitors", () => {
`Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
);
});
it("should return success message if all operations succeed", async () => {
it("should return success message if all operations succeed", async function() {
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.db.deleteAllMonitors.returns({
monitors: [{ name: "test_monitor", _id: "123" }],
@@ -736,9 +799,10 @@ describe("Monitor Controller - deleteAllMonitors", () => {
});
});
describe("Monitor Controller - editMonitor", () => {
describe("Monitor Controller - editMonitor", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {},
params: {
@@ -768,35 +832,41 @@ describe("Monitor Controller - editMonitor", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
req.params = {};
await editMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if body validation fails", async () => {
it("should reject with an error if body validation fails", async function() {
req.body = { invalid: 1 };
await editMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if getMonitorById operation fails", async () => {
it("should reject with an error if getMonitorById operation fails", async function() {
req.db.getMonitorById.throws(new Error("DB error"));
await editMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if editMonitor operation fails", async () => {
it("should reject with an error if editMonitor operation fails", async function() {
req.db.getMonitorById.returns({ teamId: "123" });
req.db.editMonitor.throws(new Error("DB error"));
await editMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if deleteNotificationsByMonitorId operation fails", async () => {
it("should reject with an error if deleteNotificationsByMonitorId operation fails", async function() {
req.db.getMonitorById.returns({ teamId: "123" });
req.db.editMonitor.returns({ _id: "123" });
req.db.deleteNotificationsByMonitorId.throws(new Error("DB error"));
@@ -804,7 +874,8 @@ describe("Monitor Controller - editMonitor", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if createNotification operation fails", async () => {
it("should reject with an error if createNotification operation fails", async function() {
req.db.getMonitorById.returns({ teamId: "123" });
req.db.editMonitor.returns({ _id: "123" });
req.db.createNotification.throws(new Error("DB error"));
@@ -812,7 +883,8 @@ describe("Monitor Controller - editMonitor", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if deleteJob operation fails", async () => {
it("should reject with an error if deleteJob operation fails", async function() {
req.db.getMonitorById.returns({ teamId: "123" });
req.db.editMonitor.returns({ _id: "123" });
req.jobQueue.deleteJob.throws(new Error("Job error"));
@@ -820,7 +892,8 @@ describe("Monitor Controller - editMonitor", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("Job error");
});
it("should reject with an error if addJob operation fails", async () => {
it("should reject with an error if addJob operation fails", async function() {
req.db.getMonitorById.returns({ teamId: "123" });
req.db.editMonitor.returns({ _id: "123" });
req.jobQueue.addJob.throws(new Error("Add Job error"));
@@ -828,7 +901,8 @@ describe("Monitor Controller - editMonitor", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("Add Job error");
});
it("should return success message with data if all operations succeed", async () => {
it("should return success message with data if all operations succeed", async function() {
const monitor = { _id: "123" };
req.db.getMonitorById.returns({ teamId: "123" });
req.db.editMonitor.returns(monitor);
@@ -844,9 +918,10 @@ describe("Monitor Controller - editMonitor", () => {
});
});
describe("Monitor Controller - pauseMonitor", () => {
describe("Monitor Controller - pauseMonitor", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {},
params: {
@@ -871,23 +946,26 @@ describe("Monitor Controller - pauseMonitor", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should reject with an error if param validation fails", async () => {
it("should reject with an error if param validation fails", async function() {
req.params = {};
await pauseMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if getMonitorById operation fails", async () => {
it("should reject with an error if getMonitorById operation fails", async function() {
req.db.getMonitorById.throws(new Error("DB error"));
await pauseMonitor(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if deleteJob operation fails", async () => {
it("should reject with an error if deleteJob operation fails", async function() {
const monitor = { _id: req.params.monitorId, isActive: true };
req.db.getMonitorById.returns(monitor);
req.jobQueue.deleteJob.throws(new Error("Delete Job error"));
@@ -896,7 +974,7 @@ describe("Monitor Controller - pauseMonitor", () => {
expect(next.firstCall.args[0].message).to.equal("Delete Job error");
});
it("should reject with an error if addJob operation fails", async () => {
it("should reject with an error if addJob operation fails", async function() {
const monitor = { _id: req.params.monitorId, isActive: false };
req.db.getMonitorById.returns(monitor);
req.jobQueue.addJob.throws(new Error("Add Job error"));
@@ -904,7 +982,8 @@ describe("Monitor Controller - pauseMonitor", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("Add Job error");
});
it("should reject with an error if monitor.save operation fails", async () => {
it("should reject with an error if monitor.save operation fails", async function() {
const monitor = {
_id: req.params.monitorId,
active: false,
@@ -915,7 +994,8 @@ describe("Monitor Controller - pauseMonitor", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("Save error");
});
it("should return success pause message with data if all operations succeed with inactive monitor", async () => {
it("should return success pause message with data if all operations succeed with inactive monitor", async function() {
const monitor = {
_id: req.params.monitorId,
isActive: false,
@@ -932,7 +1012,8 @@ describe("Monitor Controller - pauseMonitor", () => {
})
).to.be.true;
});
it("should return success resume message with data if all operations succeed with active monitor", async () => {
it("should return success resume message with data if all operations succeed with active monitor", async function() {
const monitor = {
_id: req.params.monitorId,
isActive: true,
@@ -951,9 +1032,10 @@ describe("Monitor Controller - pauseMonitor", () => {
});
});
describe("Monitor Controller - addDemoMonitors", () => {
describe("Monitor Controller - addDemoMonitors", function() {
let req, res, next, stub;
beforeEach(() => {
beforeEach(function() {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { _id: "123", teamId: "123" };
});
@@ -980,37 +1062,43 @@ describe("Monitor Controller - addDemoMonitors", () => {
};
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
stub.restore();
});
it("should reject with an error if getTokenFromHeaders fails", async () => {
it("should reject with an error if getTokenFromHeaders fails", async function() {
req.headers = {};
await addDemoMonitors(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("No auth headers");
expect(next.firstCall.args[0].status).to.equal(500);
});
it("should reject with an error if getting settings fails", async () => {
it("should reject with an error if getting settings fails", async function() {
req.settingsService.getSettings.throws(new Error("Settings error"));
await addDemoMonitors(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("Settings error");
});
it("should reject with an error if JWT validation fails", async () => {
it("should reject with an error if JWT validation fails", async function() {
stub.restore();
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
await addDemoMonitors(req, res, next);
expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
});
it("should reject with an error if addDemoMonitors operation fails", async () => {
it("should reject with an error if addDemoMonitors operation fails", async function() {
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.db.addDemoMonitors.throws(new Error("DB error"));
await addDemoMonitors(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("DB error");
});
it("should reject with an error if addJob operation fails", async () => {
it("should reject with an error if addJob operation fails", async function() {
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.db.addDemoMonitors.returns([{ _id: "123" }]);
req.jobQueue.addJob.throws(new Error("Add Job error"));
@@ -1018,7 +1106,8 @@ describe("Monitor Controller - addDemoMonitors", () => {
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("Add Job error");
});
it("should return success message with data if all operations succeed", async () => {
it("should return success message with data if all operations succeed", async function() {
const monitors = [{ _id: "123" }];
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
req.db.addDemoMonitors.returns(monitors);
@@ -8,9 +8,10 @@ import {
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("Queue Controller - getMetrics", () => {
describe("Queue Controller - getMetrics", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {},
params: {},
@@ -26,17 +27,19 @@ describe("Queue Controller - getMetrics", () => {
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should throw an error if getMetrics throws an error", async () => {
it("should throw an error if getMetrics throws an error", async function() {
req.jobQueue.getMetrics.throws(new Error("getMetrics error"));
await getMetrics(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getMetrics error");
});
it("should return a success message and data if getMetrics is successful", async () => {
it("should return a success message and data if getMetrics is successful", async function() {
const data = { data: "metrics" };
req.jobQueue.getMetrics.returns(data);
await getMetrics(req, res, next);
@@ -49,9 +52,10 @@ describe("Queue Controller - getMetrics", () => {
});
});
describe("Queue Controller - getJobs", () => {
describe("Queue Controller - getJobs", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {},
params: {},
@@ -67,16 +71,19 @@ describe("Queue Controller - getJobs", () => {
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if getJobs throws an error", async () => {
it("should reject with an error if getJobs throws an error", async function() {
req.jobQueue.getJobStats.throws(new Error("getJobs error"));
await getJobs(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getJobs error");
});
it("should return a success message and data if getJobs is successful", async () => {
it("should return a success message and data if getJobs is successful", async function() {
const data = { data: "jobs" };
req.jobQueue.getJobStats.returns(data);
await getJobs(req, res, next);
@@ -89,9 +96,10 @@ describe("Queue Controller - getJobs", () => {
});
});
describe("Queue Controller - addJob", () => {
describe("Queue Controller - addJob", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {},
params: {},
@@ -107,16 +115,19 @@ describe("Queue Controller - addJob", () => {
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if addJob throws an error", async () => {
it("should reject with an error if addJob throws an error", async function() {
req.jobQueue.addJob.throws(new Error("addJob error"));
await addJob(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("addJob error");
});
it("should return a success message if addJob is successful", async () => {
it("should return a success message if addJob is successful", async function() {
req.jobQueue.addJob.resolves();
await addJob(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
@@ -127,9 +138,10 @@ describe("Queue Controller - addJob", () => {
});
});
describe("Queue Controller - obliterateQueue", () => {
describe("Queue Controller - obliterateQueue", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {},
params: {},
@@ -145,16 +157,19 @@ describe("Queue Controller - obliterateQueue", () => {
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if obliterateQueue throws an error", async () => {
it("should reject with an error if obliterateQueue throws an error", async function() {
req.jobQueue.obliterate.throws(new Error("obliterateQueue error"));
await obliterateQueue(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("obliterateQueue error");
});
it("should return a success message if obliterateQueue is successful", async () => {
it("should return a success message if obliterateQueue is successful", async function() {
req.jobQueue.obliterate.resolves();
await obliterateQueue(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
@@ -7,9 +7,10 @@ import {
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("Settings Controller - getAppSettings", () => {
describe("Settings Controller - getAppSettings", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {},
params: {},
@@ -25,17 +26,19 @@ describe("Settings Controller - getAppSettings", () => {
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should throw an error if getSettings throws an error", async () => {
it("should throw an error if getSettings throws an error", async function() {
req.settingsService.getSettings.throws(new Error("getSettings error"));
await getAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("getSettings error");
});
it("should return a success message and data if getSettings is successful", async () => {
it("should return a success message and data if getSettings is successful", async function() {
const data = { data: "settings" };
req.settingsService.getSettings.returns(data);
await getAppSettings(req, res, next);
@@ -48,9 +51,10 @@ describe("Settings Controller - getAppSettings", () => {
});
});
describe("Settings Controller - updateAppSettings", () => {
describe("Settings Controller - updateAppSettings", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
headers: {},
params: {},
@@ -68,28 +72,33 @@ describe("Settings Controller - updateAppSettings", () => {
};
next = sinon.stub();
});
afterEach(() => {
sinon.restore();
});
it("should reject with an error if body validation fails", async () => {
it("should reject with an error if body validation fails", async function() {
req.body = { invalid: 1 };
await updateAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].status).to.equal(422);
});
it("should reject with an error if updateAppSettings throws an error", async () => {
it("should reject with an error if updateAppSettings throws an error", async function() {
req.db.updateAppSettings.throws(new Error("updateAppSettings error"));
await updateAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("updateAppSettings error");
});
it("should reject with an error if reloadSettings throws an error", async () => {
it("should reject with an error if reloadSettings throws an error", async function() {
req.settingsService.reloadSettings.throws(new Error("reloadSettings error"));
await updateAppSettings(req, res, next);
expect(next.firstCall.args[0]).to.be.an("error");
expect(next.firstCall.args[0].message).to.equal("reloadSettings error");
});
it("should return a success message and data if updateAppSettings is successful", async () => {
it("should return a success message and data if updateAppSettings is successful", async function() {
const data = { data: "settings" };
req.settingsService.reloadSettings.returns(data);
await updateAppSettings(req, res, next);
@@ -4,9 +4,10 @@ import {
getStatusPageByUrl,
} from "../../controllers/statusPageController.js";
describe("statusPageController", () => {
describe("statusPageController", function() {
let req, res, next;
beforeEach(() => {
beforeEach(function() {
req = {
params: {},
body: {},
@@ -22,12 +23,12 @@ describe("statusPageController", () => {
next = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("createStatusPage", () => {
beforeEach(() => {
describe("createStatusPage", function() {
beforeEach(function() {
req.body = {
companyName: "Test Company",
url: "123456",
@@ -38,11 +39,11 @@ describe("statusPageController", () => {
};
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should handle a validation error", async () => {
it("should handle a validation error", async function() {
req.body = {
// Invalid data that will trigger validation error
companyName: "",
@@ -60,7 +61,7 @@ describe("statusPageController", () => {
}
});
it("should handle a db error", async () => {
it("should handle a db error", async function() {
const err = new Error("DB error");
req.db.createStatusPage.throws(err);
@@ -71,24 +72,25 @@ describe("statusPageController", () => {
}
});
it("should insert a properly formatted status page", async () => {
it("should insert a properly formatted status page", async function() {
const result = await createStatusPage(req, res, next);
expect(res.status.firstCall.args[0]).to.equal(200);
expect(res.json.firstCall.args[0].success).to.be.true;
});
});
describe("getStatusPageByUrl", () => {
beforeEach(() => {
describe("getStatusPageByUrl", function() {
beforeEach(function() {
req.params = {
url: "123456",
};
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should handle a validation error", async () => {
it("should handle a validation error", async function() {
req.params = {
url: "",
};
@@ -101,7 +103,7 @@ describe("statusPageController", () => {
}
});
it("should handle a DB error", async () => {
it("should handle a DB error", async function() {
const err = new Error("DB error");
req.db.getStatusPageByUrl.throws(err);
@@ -112,7 +114,7 @@ describe("statusPageController", () => {
}
});
it("should return a status page", async () => {
it("should return a status page", async function() {
const statusPage = {
_id: "123456",
companyName: "Test Company",
+91 -63
View File
@@ -13,8 +13,8 @@ import Monitor from "../../db/models/Monitor.js";
import User from "../../db/models/User.js";
import logger from "../../utils/logger.js";
describe("checkModule", () => {
describe("createCheck", () => {
describe("checkModule", function() {
describe("createCheck", function() {
let checkCountDocumentsStub, checkSaveStub, monitorFindByIdStub, monitorSaveStub;
const mockMonitor = {
_id: "123",
@@ -23,31 +23,33 @@ describe("checkModule", () => {
save: () => this,
};
const mockCheck = { active: true };
beforeEach(() => {
beforeEach(function() {
checkSaveStub = sinon.stub(Check.prototype, "save");
checkCountDocumentsStub = sinon.stub(Check, "countDocuments");
monitorFindByIdStub = sinon.stub(Monitor, "findById");
monitorSaveStub = sinon.stub(Monitor.prototype, "save");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return undefined early if no monitor is found", async () => {
it("should return undefined early if no monitor is found", async function() {
monitorFindByIdStub.returns(null);
const check = await createCheck({ monitorId: "123" });
expect(check).to.be.undefined;
});
it("should return a check", async () => {
it("should return a check", async function() {
monitorFindByIdStub.returns(mockMonitor);
checkSaveStub.returns(mockCheck);
monitorSaveStub.returns(mockMonitor);
const check = await createCheck({ monitorId: "123", status: true });
expect(check).to.deep.equal(mockCheck);
});
it("should return a check if status is down", async () => {
it("should return a check if status is down", async function() {
mockMonitor.status = false;
monitorFindByIdStub.returns(mockMonitor);
checkSaveStub.returns(mockCheck);
@@ -55,7 +57,8 @@ describe("checkModule", () => {
const check = await createCheck({ monitorId: "123", status: false });
expect(check).to.deep.equal(mockCheck);
});
it("should return a check if uptimePercentage is undefined", async () => {
it("should return a check if uptimePercentage is undefined", async function() {
mockMonitor.uptimePercentage = undefined;
monitorFindByIdStub.returns(mockMonitor);
checkSaveStub.returns(mockCheck);
@@ -63,7 +66,8 @@ describe("checkModule", () => {
const check = await createCheck({ monitorId: "123", status: true });
expect(check).to.deep.equal(mockCheck);
});
it("should return a check if uptimePercentage is undefined and status is down", async () => {
it("should return a check if uptimePercentage is undefined and status is down", async function() {
mockMonitor.uptimePercentage = undefined;
monitorFindByIdStub.returns(mockMonitor);
checkSaveStub.returns(mockCheck);
@@ -71,7 +75,8 @@ describe("checkModule", () => {
const check = await createCheck({ monitorId: "123", status: false });
expect(check).to.deep.equal(mockCheck);
});
it("should monitor save error", async () => {
it("should monitor save error", async function() {
const err = new Error("Save Error");
monitorSaveStub.throws(err);
try {
@@ -80,7 +85,8 @@ describe("checkModule", () => {
expect(error).to.deep.equal(err);
}
});
it("should handle errors", async () => {
it("should handle errors", async function() {
const err = new Error("DB Error");
checkCountDocumentsStub.throws(err);
try {
@@ -90,18 +96,19 @@ describe("checkModule", () => {
}
});
});
describe("getChecksCount", () => {
describe("getChecksCount", function() {
let checkCountDocumentStub;
beforeEach(() => {
beforeEach(function() {
checkCountDocumentStub = sinon.stub(Check, "countDocuments");
});
afterEach(() => {
afterEach(function() {
checkCountDocumentStub.restore();
});
it("should return count with basic monitorId query", async () => {
it("should return count with basic monitorId query", async function() {
const req = {
params: { monitorId: "test123" },
query: {},
@@ -117,7 +124,7 @@ describe("checkModule", () => {
});
});
it("should include dateRange in query when provided", async () => {
it("should include dateRange in query when provided", async function() {
const req = {
params: { monitorId: "test123" },
query: { dateRange: "day" },
@@ -131,7 +138,7 @@ describe("checkModule", () => {
expect(checkCountDocumentStub.firstCall.args[0].createdAt).to.have.property("$gte");
});
it('should handle "all" filter correctly', async () => {
it('should handle "all" filter correctly', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "all" },
@@ -147,7 +154,7 @@ describe("checkModule", () => {
});
});
it('should handle "down" filter correctly', async () => {
it('should handle "down" filter correctly', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "down" },
@@ -163,7 +170,7 @@ describe("checkModule", () => {
});
});
it('should handle "resolve" filter correctly', async () => {
it('should handle "resolve" filter correctly', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "resolve" },
@@ -179,7 +186,8 @@ describe("checkModule", () => {
statusCode: 5000,
});
});
it("should handle unknown filter correctly", async () => {
it("should handle unknown filter correctly", async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "unknown" },
@@ -195,7 +203,7 @@ describe("checkModule", () => {
});
});
it("should combine dateRange and filter in query", async () => {
it("should combine dateRange and filter in query", async function() {
const req = {
params: { monitorId: "test123" },
query: {
@@ -215,10 +223,11 @@ describe("checkModule", () => {
);
});
});
describe("getChecks", () => {
describe("getChecks", function() {
let checkFindStub, monitorFindStub;
beforeEach(() => {
beforeEach(function() {
checkFindStub = sinon.stub(Check, "find").returns({
skip: sinon.stub().returns({
limit: sinon.stub().returns({
@@ -228,11 +237,11 @@ describe("checkModule", () => {
});
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return checks with basic monitorId query", async () => {
it("should return checks with basic monitorId query", async function() {
const req = {
params: { monitorId: "test123" },
query: {},
@@ -242,7 +251,8 @@ describe("checkModule", () => {
expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
});
it("should return checks with limit query", async () => {
it("should return checks with limit query", async function() {
const req = {
params: { monitorId: "test123" },
query: { limit: 10 },
@@ -253,7 +263,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
});
it("should handle pagination correctly", async () => {
it("should handle pagination correctly", async function() {
const req = {
params: { monitorId: "test123" },
query: {
@@ -267,7 +277,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
});
it("should handle dateRange filter", async () => {
it("should handle dateRange filter", async function() {
const req = {
params: { monitorId: "test123" },
query: { dateRange: "week" },
@@ -277,7 +287,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
});
it('should handle "all" filter', async () => {
it('should handle "all" filter', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "all" },
@@ -287,7 +297,8 @@ describe("checkModule", () => {
const result = await getChecks(req);
expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
});
it('should handle "down" filter', async () => {
it('should handle "down" filter', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "down" },
@@ -298,7 +309,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
});
it('should handle "resolve" filter', async () => {
it('should handle "resolve" filter', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "resolve" },
@@ -308,7 +319,8 @@ describe("checkModule", () => {
const result = await getChecks(req);
expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
});
it('should handle "unknown" filter', async () => {
it('should handle "unknown" filter', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "unknown" },
@@ -319,7 +331,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
});
it("should handle ascending sort order", async () => {
it("should handle ascending sort order", async function() {
const req = {
params: { monitorId: "test123" },
query: { sortOrder: "asc" },
@@ -330,7 +342,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal([{ id: 1 }, { id: 2 }]);
});
it("should handle error case", async () => {
it("should handle error case", async function() {
const req = {
params: { monitorId: "test123" },
query: {},
@@ -347,10 +359,12 @@ describe("checkModule", () => {
}
});
});
describe("getTeamChecks", () => {
describe("getTeamChecks", function() {
let checkFindStub, checkCountDocumentsStub, monitorFindStub;
const mockMonitors = [{ _id: "123" }];
beforeEach(() => {
beforeEach(function() {
monitorFindStub = sinon.stub(Monitor, "find").returns({
select: sinon.stub().returns(mockMonitors),
});
@@ -366,11 +380,11 @@ describe("checkModule", () => {
});
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return checks with basic monitorId query", async () => {
it("should return checks with basic monitorId query", async function() {
const req = {
params: { teamId: "test123" },
query: {},
@@ -380,7 +394,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
});
it("should handle pagination correctly", async () => {
it("should handle pagination correctly", async function() {
const req = {
params: { monitorId: "test123" },
query: { limit: 1, page: 2, rowsPerPage: 10 },
@@ -390,7 +404,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
});
it("should handle dateRange filter", async () => {
it("should handle dateRange filter", async function() {
const req = {
params: { monitorId: "test123" },
query: { dateRange: "week" },
@@ -399,7 +413,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
});
it('should handle "all" filter', async () => {
it('should handle "all" filter', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "all" },
@@ -409,7 +423,8 @@ describe("checkModule", () => {
const result = await getTeamChecks(req);
expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
});
it('should handle "down" filter', async () => {
it('should handle "down" filter', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "down" },
@@ -420,7 +435,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
});
it('should handle "resolve" filter', async () => {
it('should handle "resolve" filter', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "resolve" },
@@ -430,7 +445,8 @@ describe("checkModule", () => {
const result = await getTeamChecks(req);
expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
});
it('should handle "unknown" filter', async () => {
it('should handle "unknown" filter', async function() {
const req = {
params: { monitorId: "test123" },
query: { filter: "unknown" },
@@ -441,7 +457,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
});
it("should handle ascending sort order", async () => {
it("should handle ascending sort order", async function() {
const req = {
params: { monitorId: "test123" },
query: { sortOrder: "asc" },
@@ -452,7 +468,7 @@ describe("checkModule", () => {
expect(result).to.deep.equal({ checksCount: 2, checks: [{ id: 1 }, { id: 2 }] });
});
it("should handle error case", async () => {
it("should handle error case", async function() {
const req = {
params: { monitorId: "test123" },
query: {},
@@ -469,20 +485,24 @@ describe("checkModule", () => {
}
});
});
describe("deleteChecks", () => {
describe("deleteChecks", function() {
let checkDeleteManyStub;
beforeEach(() => {
beforeEach(function() {
checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 });
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return a value if a check is deleted", async () => {
it("should return a value if a check is deleted", async function() {
const result = await deleteChecks("123");
expect(result).to.equal(1);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
checkDeleteManyStub.throws(new Error("Database error"));
try {
await deleteChecks("123");
@@ -492,22 +512,27 @@ describe("checkModule", () => {
}
});
});
describe("deleteChecksByTeamId", () => {
describe("deleteChecksByTeamId", function() {
let mockMonitors = [{ _id: 123, save: () => this }];
let monitorFindStub, monitorSaveStub, checkDeleteManyStub;
beforeEach(() => {
beforeEach(function() {
monitorSaveStub = sinon.stub(Monitor.prototype, "save");
monitorFindStub = sinon.stub(Monitor, "find").returns(mockMonitors);
checkDeleteManyStub = sinon.stub(Check, "deleteMany").resolves({ deletedCount: 1 });
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return a deleted count", async () => {
it("should return a deleted count", async function() {
const result = await deleteChecksByTeamId("123");
expect(result).to.equal(1);
});
it("should handle errors", async () => {
it("should handle errors", async function() {
const err = new Error("DB Error");
monitorFindStub.throws(err);
try {
@@ -517,25 +542,27 @@ describe("checkModule", () => {
}
});
});
describe("updateChecksTTL", () => {
describe("updateChecksTTL", function() {
let userUpdateManyStub;
let loggerStub;
beforeEach(() => {
beforeEach(function() {
loggerStub = sinon.stub(logger, "error");
userUpdateManyStub = sinon.stub(User, "updateMany");
Check.collection = { dropIndex: sinon.stub(), createIndex: sinon.stub() };
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return undefined", async () => {
it("should return undefined", async function() {
const result = await updateChecksTTL("123", 10);
expect(result).to.be.undefined;
});
it("should log an error if dropIndex throws an error", async () => {
it("should log an error if dropIndex throws an error", async function() {
const err = new Error("Drop Index Error");
Check.collection.dropIndex.throws(err);
await updateChecksTTL("123", 10);
@@ -543,7 +570,7 @@ describe("checkModule", () => {
expect(loggerStub.firstCall.args[0].message).to.equal(err.message);
});
it("should throw an error if createIndex throws an error", async () => {
it("should throw an error if createIndex throws an error", async function() {
const err = new Error("Create Index Error");
Check.collection.createIndex.throws(err);
try {
@@ -552,7 +579,8 @@ describe("checkModule", () => {
expect(error).to.deep.equal(err);
}
});
it("should throw an error if User.updateMany throws an error", async () => {
it("should throw an error if User.updateMany throws an error", async function() {
const err = new Error("Update Many Error");
userUpdateManyStub.throws(err);
try {
+18 -11
View File
@@ -51,24 +51,25 @@ const mockMonitor = {
save: () => this,
};
describe("HardwareCheckModule", () => {
describe("HardwareCheckModule", function() {
let hardwareCheckSaveStub,
hardwareCheckCountDocumentsStub,
monitorFindByIdStub,
loggerStub;
beforeEach(() => {
beforeEach(function() {
loggerStub = sinon.stub(logger, "error");
hardwareCheckSaveStub = sinon.stub(HardwareCheck.prototype, "save");
monitorFindByIdStub = sinon.stub(Monitor, "findById");
hardwareCheckCountDocumentsStub = sinon.stub(HardwareCheck, "countDocuments");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("createHardwareCheck", () => {
it("should return a hardware check", async () => {
describe("createHardwareCheck", function() {
it("should return a hardware check", async function() {
hardwareCheckSaveStub.resolves(mockHardwareCheck);
monitorFindByIdStub.resolves(mockMonitor);
hardwareCheckCountDocumentsStub.resolves(1);
@@ -76,7 +77,8 @@ describe("HardwareCheckModule", () => {
expect(hardwareCheck).to.exist;
expect(hardwareCheck).to.deep.equal(mockHardwareCheck);
});
it("should return a hardware check for a check with status false", async () => {
it("should return a hardware check for a check with status false", async function() {
hardwareCheckSaveStub.resolves(mockHardwareCheck);
monitorFindByIdStub.resolves(mockMonitor);
hardwareCheckCountDocumentsStub.resolves(1);
@@ -84,7 +86,8 @@ describe("HardwareCheckModule", () => {
expect(hardwareCheck).to.exist;
expect(hardwareCheck).to.deep.equal(mockHardwareCheck);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
monitorFindByIdStub.resolves(mockMonitor);
hardwareCheckSaveStub.rejects(err);
@@ -95,20 +98,23 @@ describe("HardwareCheckModule", () => {
expect(error).to.deep.equal(err);
}
});
it("should log an error if a monitor is not found", async () => {
it("should log an error if a monitor is not found", async function() {
monitorFindByIdStub.resolves(null);
const res = await createHardwareCheck({});
expect(loggerStub.calledOnce).to.be.true;
expect(res).to.be.null;
});
it("should handle a monitor with undefined uptimePercentage", async () => {
it("should handle a monitor with undefined uptimePercentage", async function() {
monitorFindByIdStub.resolves({ ...mockMonitor, uptimePercentage: undefined });
hardwareCheckSaveStub.resolves(mockHardwareCheck);
hardwareCheckCountDocumentsStub.resolves(1);
const res = await createHardwareCheck({});
expect(res).to.exist;
});
it("should handle a monitor with undefined uptimePercentage and true status", async () => {
it("should handle a monitor with undefined uptimePercentage and true status", async function() {
monitorFindByIdStub.resolves({
...mockMonitor,
uptimePercentage: undefined,
@@ -118,7 +124,8 @@ describe("HardwareCheckModule", () => {
const res = await createHardwareCheck({ status: true });
expect(res).to.exist;
});
it("should handle a monitor with undefined uptimePercentage and false status", async () => {
it("should handle a monitor with undefined uptimePercentage and false status", async function() {
monitorFindByIdStub.resolves({
...mockMonitor,
uptimePercentage: undefined,
+16 -14
View File
@@ -7,7 +7,7 @@ import {
} from "../../db/mongo/modules/inviteModule.js";
import { errorMessages } from "../../utils/messages.js";
describe("Invite Module", () => {
describe("Invite Module", function() {
const mockUserData = {
email: "test@test.com",
teamId: "123",
@@ -19,18 +19,20 @@ describe("Invite Module", () => {
inviteTokenSaveStub,
inviteTokenFindOneStub,
inviteTokenFindOneAndDeleteStub;
beforeEach(() => {
beforeEach(function() {
inviteTokenDeleteManyStub = sinon.stub(InviteToken, "deleteMany");
inviteTokenSaveStub = sinon.stub(InviteToken.prototype, "save");
inviteTokenFindOneStub = sinon.stub(InviteToken, "findOne");
inviteTokenFindOneAndDeleteStub = sinon.stub(InviteToken, "findOneAndDelete");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("requestInviteToken", () => {
it("should return a new invite token", async () => {
describe("requestInviteToken", function() {
it("should return a new invite token", async function() {
inviteTokenDeleteManyStub.resolves();
inviteTokenSaveStub.resolves();
const inviteToken = await requestInviteToken(mockUserData);
@@ -39,7 +41,7 @@ describe("Invite Module", () => {
expect(inviteToken.token).to.exist;
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
inviteTokenDeleteManyStub.rejects(err);
try {
@@ -50,14 +52,14 @@ describe("Invite Module", () => {
});
});
describe("getInviteToken", () => {
it("should return an invite token", async () => {
describe("getInviteToken", function() {
it("should return an invite token", async function() {
inviteTokenFindOneStub.resolves(mockInviteToken);
const inviteToken = await getInviteToken(mockUserData.token);
expect(inviteToken).to.deep.equal(mockInviteToken);
});
it("should handle a token not found", async () => {
it("should handle a token not found", async function() {
inviteTokenFindOneStub.resolves(null);
try {
await getInviteToken(mockUserData.token);
@@ -66,7 +68,7 @@ describe("Invite Module", () => {
}
});
it("should handle DB errors", async () => {
it("should handle DB errors", async function() {
const err = new Error("test error");
inviteTokenFindOneStub.rejects(err);
try {
@@ -78,14 +80,14 @@ describe("Invite Module", () => {
});
});
describe("getInviteTokenAndDelete", () => {
it("should return a deleted invite", async () => {
describe("getInviteTokenAndDelete", function() {
it("should return a deleted invite", async function() {
inviteTokenFindOneAndDeleteStub.resolves(mockInviteToken);
const deletedInvite = await getInviteTokenAndDelete(mockUserData.token);
expect(deletedInvite).to.deep.equal(mockInviteToken);
});
it("should handle a token not found", async () => {
it("should handle a token not found", async function() {
inviteTokenFindOneAndDeleteStub.resolves(null);
try {
await getInviteTokenAndDelete(mockUserData.token);
@@ -94,7 +96,7 @@ describe("Invite Module", () => {
}
});
it("should handle DB errors", async () => {
it("should handle DB errors", async function() {
const err = new Error("test error");
inviteTokenFindOneAndDeleteStub.rejects(err);
try {
+49 -33
View File
@@ -11,7 +11,7 @@ import {
editMaintenanceWindowById,
} from "../../db/mongo/modules/maintenanceWindowModule.js";
describe("MaintenanceWindow Module", () => {
describe("MaintenanceWindow Module", function() {
const mockMaintenanceWindow = {
monitorId: "123",
active: true,
@@ -29,7 +29,7 @@ describe("MaintenanceWindow Module", () => {
maintenanceWindowDeleteManyStub,
maintenanceWindowFindByIdAndUpdateStub;
beforeEach(() => {
beforeEach(function() {
maintenanceWindowSaveStub = sinon.stub(MaintenanceWindow.prototype, "save");
maintenanceWindowFindByIdStub = sinon.stub(MaintenanceWindow, "findById");
maintenanceWindowCountDocumentsStub = sinon.stub(MaintenanceWindow, "countDocuments");
@@ -51,18 +51,18 @@ describe("MaintenanceWindow Module", () => {
);
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("createMaintenanceWindow", () => {
it("should save a new maintenance window", async () => {
describe("createMaintenanceWindow", function() {
it("should save a new maintenance window", async function() {
maintenanceWindowSaveStub.resolves(mockMaintenanceWindow);
const result = await createMaintenanceWindow(mockMaintenanceWindow);
expect(result).to.deep.equal(mockMaintenanceWindow);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
maintenanceWindowSaveStub.rejects(err);
try {
@@ -72,13 +72,15 @@ describe("MaintenanceWindow Module", () => {
}
});
});
describe("getMaintenanceWindowById", () => {
it("should return a maintenance window", async () => {
describe("getMaintenanceWindowById", function() {
it("should return a maintenance window", async function() {
maintenanceWindowFindByIdStub.resolves(mockMaintenanceWindow);
const result = await getMaintenanceWindowById(mockMaintenanceWindow.id);
expect(result).to.deep.equal(mockMaintenanceWindow);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
maintenanceWindowFindByIdStub.rejects(err);
try {
@@ -89,9 +91,10 @@ describe("MaintenanceWindow Module", () => {
});
});
describe("getMaintenanceWindowsByTeamId", () => {
describe("getMaintenanceWindowsByTeamId", function() {
let query;
beforeEach(() => {
beforeEach(function() {
query = {
active: true,
page: 1,
@@ -101,11 +104,11 @@ describe("MaintenanceWindow Module", () => {
};
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return a list of maintenance windows and count", async () => {
it("should return a list of maintenance windows and count", async function() {
maintenanceWindowCountDocumentsStub.resolves(1);
const result = await getMaintenanceWindowsByTeamId(
mockMaintenanceWindow.teamId,
@@ -116,7 +119,8 @@ describe("MaintenanceWindow Module", () => {
maintenanceWindowCount: 1,
});
});
it("should return a list of maintenance windows and count with empty query", async () => {
it("should return a list of maintenance windows and count with empty query", async function() {
query = undefined;
maintenanceWindowCountDocumentsStub.resolves(1);
const result = await getMaintenanceWindowsByTeamId(
@@ -128,7 +132,8 @@ describe("MaintenanceWindow Module", () => {
maintenanceWindowCount: 1,
});
});
it("should return a list of maintenance windows and count with no pagination provided", async () => {
it("should return a list of maintenance windows and count with no pagination provided", async function() {
query.page = undefined;
query.rowsPerPage = undefined;
maintenanceWindowCountDocumentsStub.resolves(1);
@@ -141,7 +146,8 @@ describe("MaintenanceWindow Module", () => {
maintenanceWindowCount: 1,
});
});
it("should return a list of maintenance windows and count with field and desc order", async () => {
it("should return a list of maintenance windows and count with field and desc order", async function() {
query.order = "desc";
maintenanceWindowCountDocumentsStub.resolves(1);
const result = await getMaintenanceWindowsByTeamId(
@@ -153,7 +159,8 @@ describe("MaintenanceWindow Module", () => {
maintenanceWindowCount: 1,
});
});
it("should return a list of maintenance windows and count no field", async () => {
it("should return a list of maintenance windows and count no field", async function() {
query.field = undefined;
maintenanceWindowCountDocumentsStub.resolves(1);
const result = await getMaintenanceWindowsByTeamId(
@@ -165,7 +172,8 @@ describe("MaintenanceWindow Module", () => {
maintenanceWindowCount: 1,
});
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
maintenanceWindowCountDocumentsStub.rejects(err);
try {
@@ -175,15 +183,17 @@ describe("MaintenanceWindow Module", () => {
}
});
});
describe("getMaintenanceWindowsByMonitorId", () => {
it("should return a list of maintenance windows", async () => {
describe("getMaintenanceWindowsByMonitorId", function() {
it("should return a list of maintenance windows", async function() {
maintenanceWindowFindStub.resolves(mockMaintenanceWindows);
const result = await getMaintenanceWindowsByMonitorId(
mockMaintenanceWindow.monitorId
);
expect(result).to.deep.equal(mockMaintenanceWindows);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
maintenanceWindowFindStub.rejects(err);
try {
@@ -193,13 +203,15 @@ describe("MaintenanceWindow Module", () => {
}
});
});
describe("deleteMaintenanceWindowById", () => {
it("should delete a maintenance window", async () => {
describe("deleteMaintenanceWindowById", function() {
it("should delete a maintenance window", async function() {
maintenanceWindowFindByIdAndDeleteStub.resolves(mockMaintenanceWindow);
const result = await deleteMaintenanceWindowById(mockMaintenanceWindow.id);
expect(result).to.deep.equal(mockMaintenanceWindow);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
maintenanceWindowFindByIdAndDeleteStub.rejects(err);
try {
@@ -210,15 +222,16 @@ describe("MaintenanceWindow Module", () => {
});
});
describe("deleteMaintenanceWindowByMonitorId", () => {
it("should return the number of documents deleted", async () => {
describe("deleteMaintenanceWindowByMonitorId", function() {
it("should return the number of documents deleted", async function() {
maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 });
const result = await deleteMaintenanceWindowByMonitorId(
mockMaintenanceWindow.monitorId
);
expect(result).to.deep.equal({ deletedCount: 1 });
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
maintenanceWindowDeleteManyStub.rejects(err);
try {
@@ -229,13 +242,14 @@ describe("MaintenanceWindow Module", () => {
});
});
describe("deleteMaintenanceWindowByUserId", () => {
it("should return the number of documents deleted", async () => {
describe("deleteMaintenanceWindowByUserId", function() {
it("should return the number of documents deleted", async function() {
maintenanceWindowDeleteManyStub.resolves({ deletedCount: 1 });
const result = await deleteMaintenanceWindowByUserId(mockMaintenanceWindow.userId);
expect(result).to.deep.equal({ deletedCount: 1 });
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
maintenanceWindowDeleteManyStub.rejects(err);
try {
@@ -245,8 +259,9 @@ describe("MaintenanceWindow Module", () => {
}
});
});
describe("editMaintenanceWindowById", () => {
it("should return the updated maintenance window", async () => {
describe("editMaintenanceWindowById", function() {
it("should return the updated maintenance window", async function() {
maintenanceWindowFindByIdAndUpdateStub.resolves(mockMaintenanceWindow);
const result = await editMaintenanceWindowById(
mockMaintenanceWindow.id,
@@ -254,7 +269,8 @@ describe("MaintenanceWindow Module", () => {
);
expect(result).to.deep.equal(mockMaintenanceWindow);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
maintenanceWindowFindByIdAndUpdateStub.rejects(err);
try {
+164 -139
View File
@@ -31,7 +31,7 @@ import {
calculateGroupStats,
} from "../../db/mongo/modules/monitorModule.js";
describe("monitorModule", () => {
describe("monitorModule", function() {
let monitorFindStub,
monitorFindByIdStub,
monitorFindByIdAndUpdateStub,
@@ -42,7 +42,8 @@ describe("monitorModule", () => {
checkFindStub,
pageSpeedCheckFindStub,
hardwareCheckFindStub;
beforeEach(() => {
beforeEach(function() {
monitorFindStub = sinon.stub(Monitor, "find");
monitorFindByIdStub = sinon.stub(Monitor, "findById");
monitorFindByIdAndUpdateStub = sinon.stub(Monitor, "findByIdAndUpdate");
@@ -61,12 +62,13 @@ describe("monitorModule", () => {
sort: sinon.stub(),
});
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("getAllMonitors", () => {
it("should return all monitors", async () => {
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" },
@@ -78,12 +80,14 @@ describe("monitorModule", () => {
expect(monitorFindStub.calledOnce).to.be.true;
expect(monitorFindStub.firstCall.args).to.deep.equal([]);
});
it("should handle empty results", async () => {
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 () => {
it("should throw error when database fails", async function() {
// Arrange
const error = new Error("Database error");
error.service = "MonitorModule";
@@ -101,8 +105,8 @@ describe("monitorModule", () => {
});
});
describe("getAllMonitorsWithUptimeStats", () => {
it("should return monitors with uptime stats for different time periods", async () => {
describe("getAllMonitorsWithUptimeStats", function() {
it("should return monitors with uptime stats for different time periods", async function() {
// Mock data
const mockMonitors = [
{
@@ -147,7 +151,8 @@ describe("monitorModule", () => {
expect(monitor["30"]).to.equal(75);
expect(monitor["90"]).to.equal(75);
});
it("should return monitors with stats for pagespeed type", async () => {
it("should return monitors with stats for pagespeed type", async function() {
// Mock data
const mockMonitors = [
{
@@ -192,7 +197,8 @@ describe("monitorModule", () => {
expect(monitor["30"]).to.equal(75);
expect(monitor["90"]).to.equal(75);
});
it("should return monitors with stats for hardware type", async () => {
it("should return monitors with stats for hardware type", async function() {
// Mock data
const mockMonitors = [
{
@@ -238,7 +244,7 @@ describe("monitorModule", () => {
expect(monitor["90"]).to.equal(75);
});
it("should handle errors appropriately", async () => {
it("should handle errors appropriately", async function() {
// Setup stub to throw error
monitorFindStub.rejects(new Error("Database error"));
@@ -251,7 +257,8 @@ describe("monitorModule", () => {
expect(error.method).to.equal("getAllMonitorsWithUptimeStats");
}
});
it("should handle empty monitor list", async () => {
it("should handle empty monitor list", async function() {
monitorFindStub.resolves([]);
const result = await getAllMonitorsWithUptimeStats();
@@ -260,7 +267,7 @@ describe("monitorModule", () => {
expect(result).to.have.lengthOf(0);
});
it("should handle monitor with no checks", async () => {
it("should handle monitor with no checks", async function() {
const mockMonitors = [
{
_id: "monitor1",
@@ -285,28 +292,28 @@ describe("monitorModule", () => {
});
});
describe("calculateUptimeDuration", () => {
describe("calculateUptimeDuration", function() {
let clock;
const NOW = new Date("2024-01-01T12:00:00Z").getTime();
beforeEach(() => {
beforeEach(function() {
// Fix the current time
clock = sinon.useFakeTimers(NOW);
});
afterEach(() => {
afterEach(function() {
clock.restore();
});
it("should return 0 when checks array is empty", () => {
it("should return 0 when checks array is empty", function() {
expect(calculateUptimeDuration([])).to.equal(0);
});
it("should return 0 when checks array is null", () => {
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", () => {
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" },
@@ -318,7 +325,7 @@ describe("monitorModule", () => {
expect(calculateUptimeDuration(checks)).to.equal(7200000);
});
it("should calculate uptime from first check when no down checks exist", () => {
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" },
@@ -330,28 +337,28 @@ describe("monitorModule", () => {
});
});
describe("getLastChecked", () => {
describe("getLastChecked", function() {
let clock;
const NOW = new Date("2024-01-01T12:00:00Z").getTime();
beforeEach(() => {
beforeEach(function() {
// Fix the current time
clock = sinon.useFakeTimers(NOW);
});
afterEach(() => {
afterEach(function() {
clock.restore();
});
it("should return 0 when checks array is empty", () => {
it("should return 0 when checks array is empty", function() {
expect(getLastChecked([])).to.equal(0);
});
it("should return 0 when checks array is null", () => {
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", () => {
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" },
@@ -362,7 +369,7 @@ describe("monitorModule", () => {
expect(getLastChecked(checks)).to.equal(1800000);
});
it("should handle checks from different days", () => {
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" },
@@ -372,16 +379,17 @@ describe("monitorModule", () => {
expect(getLastChecked(checks)).to.equal(86400000);
});
});
describe("getLatestResponseTime", () => {
it("should return 0 when checks array is empty", () => {
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", () => {
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", () => {
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" },
@@ -391,7 +399,7 @@ describe("monitorModule", () => {
expect(getLatestResponseTime(checks)).to.equal(150);
});
it("should handle missing responseTime in checks", () => {
it("should handle missing responseTime in checks", function() {
const checks = [
{ createdAt: "2024-01-01T11:30:00Z" },
{ responseTime: 200, createdAt: "2024-01-01T11:00:00Z" },
@@ -400,16 +408,17 @@ describe("monitorModule", () => {
expect(getLatestResponseTime(checks)).to.equal(0);
});
});
describe("getAverageResponseTime", () => {
it("should return 0 when checks array is empty", () => {
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", () => {
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", () => {
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" },
@@ -420,7 +429,7 @@ describe("monitorModule", () => {
expect(getAverageResponseTime(checks)).to.equal(200);
});
it("should handle missing responseTime in some checks", () => {
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" },
@@ -431,7 +440,7 @@ describe("monitorModule", () => {
expect(getAverageResponseTime(checks)).to.equal(200);
});
it("should return 0 when no checks have responseTime", () => {
it("should return 0 when no checks have responseTime", function() {
const checks = [
{ createdAt: "2024-01-01T11:30:00Z" },
{ createdAt: "2024-01-01T11:00:00Z" },
@@ -440,26 +449,27 @@ describe("monitorModule", () => {
expect(getAverageResponseTime(checks)).to.equal(0);
});
});
describe("getUptimePercentage", () => {
it("should return 0 when checks array is empty", () => {
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", () => {
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", () => {
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", () => {
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", () => {
it("should calculate correct percentage for mixed status checks", function() {
const checks = [
{ status: true },
{ status: false },
@@ -470,32 +480,33 @@ describe("monitorModule", () => {
expect(getUptimePercentage(checks)).to.equal(75);
});
it("should handle undefined status values", () => {
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", () => {
it("should return 0 when checks array is empty", () => {
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", () => {
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", () => {
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", () => {
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", () => {
it("should count correct number of incidents for mixed status checks", function() {
const checks = [
{ status: true },
{ status: false },
@@ -506,7 +517,7 @@ describe("monitorModule", () => {
expect(getIncidents(checks)).to.equal(2);
});
it("should handle undefined status values", () => {
it("should handle undefined status values", function() {
const checks = [
{ status: true },
{ status: undefined },
@@ -517,10 +528,11 @@ describe("monitorModule", () => {
expect(getIncidents(checks)).to.equal(2);
});
});
describe("getMonitorChecks", () => {
describe("getMonitorChecks", function() {
let mockModel;
beforeEach(() => {
beforeEach(function() {
// Create a mock model with chainable methods
const mockChecks = [
{ monitorId: "123", createdAt: new Date("2024-01-01") },
@@ -534,11 +546,11 @@ describe("monitorModule", () => {
};
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return all checks and date-ranged checks", async () => {
it("should return all checks and date-ranged checks", async function() {
// Arrange
const monitorId = "123";
const dateRange = {
@@ -567,7 +579,7 @@ describe("monitorModule", () => {
});
});
it("should handle empty results", async () => {
it("should handle empty results", async function() {
// Arrange
const emptyModel = {
find: sinon.stub().returns({
@@ -591,7 +603,7 @@ describe("monitorModule", () => {
expect(result.checksForDateRange).to.be.an("array").that.is.empty;
});
it("should maintain sort order", async () => {
it("should maintain sort order", async function() {
// Arrange
const sortedChecks = [
{ monitorId: "123", createdAt: new Date("2024-01-02") },
@@ -625,39 +637,39 @@ describe("monitorModule", () => {
});
});
describe("processChecksForDisplay", () => {
describe("processChecksForDisplay", function() {
let normalizeStub;
beforeEach(() => {
beforeEach(function() {
normalizeStub = sinon.stub();
});
it("should return original checks when numToDisplay is not provided", () => {
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", () => {
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", () => {
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", () => {
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", () => {
it("should call normalizeData when normalize is true", function() {
const checks = [1, 2, 3];
normalizeStub.returns([10, 20, 30]);
@@ -667,7 +679,7 @@ describe("monitorModule", () => {
expect(result).to.deep.equal([10, 20, 30]);
});
it("should handle both filtering and normalization", () => {
it("should handle both filtering and normalization", function() {
const checks = [1, 2, 3, 4, 5, 6];
normalizeStub.returns([10, 30, 50]);
@@ -677,7 +689,8 @@ describe("monitorModule", () => {
expect(result).to.deep.equal([10, 30, 50]);
});
});
describe("groupChecksByTime", () => {
describe("groupChecksByTime", function() {
const mockChecks = [
{ createdAt: "2024-01-15T10:30:45Z" },
{ createdAt: "2024-01-15T10:45:15Z" },
@@ -685,7 +698,7 @@ describe("monitorModule", () => {
{ createdAt: "2024-01-16T10:30:00Z" },
];
it("should group checks by hour when dateRange is 'day'", () => {
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
@@ -700,7 +713,7 @@ describe("monitorModule", () => {
expect(result[time3].checks).to.have.lengthOf(1);
});
it("should group checks by day when dateRange is not 'day'", () => {
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);
@@ -708,12 +721,12 @@ describe("monitorModule", () => {
expect(result["2024-01-16"].checks).to.have.lengthOf(1);
});
it("should handle empty checks array", () => {
it("should handle empty checks array", function() {
const result = groupChecksByTime([], "day");
expect(result).to.deep.equal({});
});
it("should handle single check", () => {
it("should handle single check", function() {
const singleCheck = [{ createdAt: "2024-01-15T10:30:45Z" }];
const result = groupChecksByTime(singleCheck, "day");
@@ -721,7 +734,8 @@ describe("monitorModule", () => {
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", () => {
it("should skip invalid dates and process valid ones", function() {
const checksWithInvalidDate = [
{ createdAt: "invalid-date" },
{ createdAt: "2024-01-15T10:30:45Z" },
@@ -737,7 +751,8 @@ describe("monitorModule", () => {
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", () => {
it("should handle checks in same time group", function() {
const checksInSameHour = [
{ createdAt: "2024-01-15T10:15:00Z" },
{ createdAt: "2024-01-15T10:45:00Z" },
@@ -750,16 +765,17 @@ describe("monitorModule", () => {
expect(result[expectedTime].checks).to.have.lengthOf(2);
});
});
describe("calculateGroupStats", () => {
describe("calculateGroupStats", function() {
// Mock getUptimePercentage function
let uptimePercentageStub;
beforeEach(() => {
beforeEach(function() {
uptimePercentageStub = sinon.stub();
uptimePercentageStub.returns(95); // Default return value
});
it("should calculate stats correctly for a group of checks", () => {
it("should calculate stats correctly for a group of checks", function() {
const mockGroup = {
time: "2024-01-15",
checks: [
@@ -780,7 +796,7 @@ describe("monitorModule", () => {
});
});
it("should handle empty checks array", () => {
it("should handle empty checks array", function() {
const mockGroup = {
time: "2024-01-15",
checks: [],
@@ -797,7 +813,7 @@ describe("monitorModule", () => {
});
});
it("should handle missing responseTime values", () => {
it("should handle missing responseTime values", function() {
const mockGroup = {
time: "2024-01-15",
checks: [
@@ -818,7 +834,7 @@ describe("monitorModule", () => {
});
});
it("should handle all checks with status false", () => {
it("should handle all checks with status false", function() {
const mockGroup = {
time: "2024-01-15",
checks: [
@@ -839,7 +855,7 @@ describe("monitorModule", () => {
});
});
it("should handle all checks with status true", () => {
it("should handle all checks with status true", function() {
const mockGroup = {
time: "2024-01-15",
checks: [
@@ -861,7 +877,7 @@ describe("monitorModule", () => {
});
});
describe("getMonitorStatsById", () => {
describe("getMonitorStatsById", function() {
const now = new Date();
const oneHourAgo = new Date(now - 3600000);
const twoHoursAgo = new Date(now - 7200000);
@@ -958,18 +974,18 @@ describe("monitorModule", () => {
},
};
beforeEach(() => {
beforeEach(function() {
checkFindStub.returns({
sort: () => checkDocs,
});
monitorFindByIdStub.returns(mockMonitor);
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return monitor stats with calculated values, sort order desc", async () => {
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([
@@ -992,7 +1008,8 @@ describe("monitorModule", () => {
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 () => {
it("should return monitor stats with calculated values, ping type", async function() {
monitorFindByIdStub.returns(mockMonitorPing);
req.query.sortOrder = "desc";
const result = await getMonitorStatsById(req);
@@ -1016,7 +1033,8 @@ describe("monitorModule", () => {
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 () => {
it("should return monitor stats with calculated values, docker type", async function() {
monitorFindByIdStub.returns(mockMonitorDocker);
req.query.sortOrder = "desc";
const result = await getMonitorStatsById(req);
@@ -1040,7 +1058,8 @@ describe("monitorModule", () => {
expect(result.periodUptime).to.be.a("number");
expect(result.aggregateData).to.be.an("array");
});
it("should return monitor stats with calculated values", async () => {
it("should return monitor stats with calculated values", async function() {
req.query.sortOrder = "asc";
const result = await getMonitorStatsById(req);
expect(result).to.include.keys([
@@ -1063,7 +1082,8 @@ describe("monitorModule", () => {
expect(result.periodUptime).to.be.a("number");
expect(result.aggregateData).to.be.an("array");
});
it("should throw error when monitor is not found", async () => {
it("should throw error when monitor is not found", async function() {
monitorFindByIdStub.returns(Promise.resolve(null));
const req = {
@@ -1081,21 +1101,21 @@ describe("monitorModule", () => {
});
});
describe("getMonitorById", () => {
describe("getMonitorById", function() {
let notificationFindStub;
let monitorSaveStub;
beforeEach(() => {
beforeEach(function() {
// Create stubs
notificationFindStub = sinon.stub(Notification, "find");
monitorSaveStub = sinon.stub().resolves();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return monitor with notifications when found", async () => {
it("should return monitor with notifications when found", async function() {
// Arrange
const monitorId = "123";
const mockMonitor = {
@@ -1119,7 +1139,7 @@ describe("monitorModule", () => {
expect(monitorSaveStub.calledOnce).to.be.true;
});
it("should throw 404 error when monitor not found", async () => {
it("should throw 404 error when monitor not found", async function() {
// Arrange
const monitorId = "nonexistent";
monitorFindByIdStub.resolves(null);
@@ -1136,7 +1156,7 @@ describe("monitorModule", () => {
}
});
it("should handle database errors properly", async () => {
it("should handle database errors properly", async function() {
// Arrange
const monitorId = "123";
const dbError = new Error("Database connection failed");
@@ -1153,7 +1173,7 @@ describe("monitorModule", () => {
}
});
it("should handle notification fetch errors", async () => {
it("should handle notification fetch errors", async function() {
// Arrange
const monitorId = "123";
const mockMonitor = {
@@ -1177,7 +1197,7 @@ describe("monitorModule", () => {
}
});
it("should handle monitor save errors", async () => {
it("should handle monitor save errors", async function() {
// Arrange
const monitorId = "123";
const mockMonitor = {
@@ -1201,8 +1221,9 @@ describe("monitorModule", () => {
}
});
});
describe("getMonitorsAndSummaryByTeamId", () => {
it("should return monitors and correct summary counts", async () => {
describe("getMonitorsAndSummaryByTeamId", function() {
it("should return monitors and correct summary counts", async function() {
// Arrange
const teamId = "team123";
const type = "http";
@@ -1228,7 +1249,7 @@ describe("monitorModule", () => {
expect(monitorFindStub.calledOnceWith({ teamId, type })).to.be.true;
});
it("should return empty results for non-existent team", async () => {
it("should return empty results for non-existent team", async function() {
// Arrange
monitorFindStub.resolves([]);
@@ -1245,7 +1266,7 @@ describe("monitorModule", () => {
});
});
it("should handle database errors", async () => {
it("should handle database errors", async function() {
// Arrange
const error = new Error("Database error");
error.service = "MonitorModule";
@@ -1263,8 +1284,9 @@ describe("monitorModule", () => {
}
});
});
describe("getMonitorsByTeamId", () => {
beforeEach(() => {
describe("getMonitorsByTeamId", function() {
beforeEach(function() {
// Chain stubs for Monitor.find().skip().limit().sort()
// Stub for CHECK_MODEL_LOOKUP model find
@@ -1275,11 +1297,11 @@ describe("monitorModule", () => {
});
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should return monitors with basic query parameters", async () => {
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" }) },
@@ -1312,7 +1334,7 @@ describe("monitorModule", () => {
expect(result).to.have.property("monitorCount", 2);
});
it("should return monitors with basic query parameters", async () => {
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" }) },
@@ -1345,7 +1367,7 @@ describe("monitorModule", () => {
expect(result).to.have.property("monitorCount", 2);
});
it("should handle type filter with array input", async () => {
it("should handle type filter with array input", async function() {
const req = {
params: { teamId: "team123" },
query: {
@@ -1370,7 +1392,7 @@ describe("monitorModule", () => {
});
});
it("should handle text search filter", async () => {
it("should handle text search filter", async function() {
const req = {
params: { teamId: "team123" },
query: {
@@ -1398,7 +1420,7 @@ describe("monitorModule", () => {
});
});
it("should handle pagination parameters", async () => {
it("should handle pagination parameters", async function() {
const req = {
params: { teamId: "team123" },
query: {
@@ -1423,7 +1445,7 @@ describe("monitorModule", () => {
});
});
it("should handle sorting parameters", async () => {
it("should handle sorting parameters", async function() {
const req = {
params: { teamId: "team123" },
query: {
@@ -1450,7 +1472,7 @@ describe("monitorModule", () => {
});
});
it("should return early when limit is -1", async () => {
it("should return early when limit is -1", async function() {
// Arrange
const req = {
params: { teamId: "team123" },
@@ -1484,7 +1506,7 @@ describe("monitorModule", () => {
});
});
it("should normalize checks when normalize parameter is provided", async () => {
it("should normalize checks when normalize parameter is provided", async function() {
const req = {
params: { teamId: "team123" },
query: { normalize: "true" },
@@ -1508,7 +1530,8 @@ describe("monitorModule", () => {
expect(result.monitorCount).to.equal(2);
expect(result.monitors).to.have.lengthOf(2);
});
it("should handle database errors", async () => {
it("should handle database errors", async function() {
const req = {
params: { teamId: "team123" },
query: {},
@@ -1533,8 +1556,9 @@ describe("monitorModule", () => {
}
});
});
describe("createMonitor", () => {
it("should create a monitor without notifications", async () => {
describe("createMonitor", function() {
it("should create a monitor without notifications", async function() {
let monitorSaveStub = sinon.stub(Monitor.prototype, "save").resolves();
const req = {
@@ -1558,7 +1582,8 @@ describe("monitorModule", () => {
expect(result.name).to.equal(expectedMonitor.name);
expect(result.url).to.equal(expectedMonitor.url);
});
it("should handle database errors", async () => {
it("should handle database errors", async function() {
const req = {
body: {
name: "Test Monitor",
@@ -1575,8 +1600,8 @@ describe("monitorModule", () => {
});
});
describe("deleteMonitor", () => {
it("should delete a monitor successfully", async () => {
describe("deleteMonitor", function() {
it("should delete a monitor successfully", async function() {
const monitorId = "123456789";
const mockMonitor = {
_id: monitorId,
@@ -1596,7 +1621,7 @@ describe("monitorModule", () => {
sinon.assert.calledWith(monitorFindByIdAndDeleteStub, monitorId);
});
it("should throw error when monitor not found", async () => {
it("should throw error when monitor not found", async function() {
const monitorId = "nonexistent123";
const req = {
params: { monitorId },
@@ -1614,7 +1639,7 @@ describe("monitorModule", () => {
}
});
it("should handle database errors", async () => {
it("should handle database errors", async function() {
const monitorId = "123456789";
const req = {
params: { monitorId },
@@ -1634,8 +1659,8 @@ describe("monitorModule", () => {
});
});
describe("deleteAllMonitors", () => {
it("should delete all monitors for a team successfully", async () => {
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 },
@@ -1655,7 +1680,7 @@ describe("monitorModule", () => {
sinon.assert.calledWith(monitorDeleteManyStub, { teamId });
});
it("should return empty array when no monitors found", async () => {
it("should return empty array when no monitors found", async function() {
const teamId = "emptyTeam";
monitorFindStub.resolves([]);
@@ -1671,7 +1696,7 @@ describe("monitorModule", () => {
sinon.assert.calledWith(monitorDeleteManyStub, { teamId });
});
it("should handle database errors", async () => {
it("should handle database errors", async function() {
const teamId = "team123";
const dbError = new Error("Database connection error");
monitorFindStub.rejects(dbError);
@@ -1685,7 +1710,7 @@ describe("monitorModule", () => {
}
});
it("should handle deleteMany errors", async () => {
it("should handle deleteMany errors", async function() {
const teamId = "team123";
monitorFindStub.resolves([{ _id: "1", name: "Monitor 1" }]);
monitorDeleteManyStub.rejects(new Error("Delete operation failed"));
@@ -1700,14 +1725,14 @@ describe("monitorModule", () => {
});
});
describe("deleteMonitorsByUserId", () => {
beforeEach(() => {});
describe("deleteMonitorsByUserId", function() {
beforeEach(function() {});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should delete all monitors for a user successfully", async () => {
it("should delete all monitors for a user successfully", async function() {
// Arrange
const userId = "user123";
const mockResult = {
@@ -1725,7 +1750,7 @@ describe("monitorModule", () => {
sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId });
});
it("should return zero deletedCount when no monitors found", async () => {
it("should return zero deletedCount when no monitors found", async function() {
// Arrange
const userId = "nonexistentUser";
const mockResult = {
@@ -1743,7 +1768,7 @@ describe("monitorModule", () => {
sinon.assert.calledWith(monitorDeleteManyStub, { userId: userId });
});
it("should handle database errors", async () => {
it("should handle database errors", async function() {
// Arrange
const userId = "user123";
const dbError = new Error("Database connection error");
@@ -1761,8 +1786,8 @@ describe("monitorModule", () => {
});
});
describe("editMonitor", () => {
it("should edit a monitor successfully", async () => {
describe("editMonitor", function() {
it("should edit a monitor successfully", async function() {
// Arrange
const candidateId = "monitor123";
const candidateMonitor = {
@@ -1801,7 +1826,7 @@ describe("monitorModule", () => {
);
});
it("should return null when monitor not found", async () => {
it("should return null when monitor not found", async function() {
// Arrange
const candidateId = "nonexistent123";
const candidateMonitor = {
@@ -1823,7 +1848,7 @@ describe("monitorModule", () => {
);
});
it("should remove notifications from update data", async () => {
it("should remove notifications from update data", async function() {
// Arrange
const candidateId = "monitor123";
const candidateMonitor = {
@@ -1855,7 +1880,7 @@ describe("monitorModule", () => {
);
});
it("should handle database errors", async () => {
it("should handle database errors", async function() {
// Arrange
const candidateId = "monitor123";
const candidateMonitor = {
@@ -1877,8 +1902,8 @@ describe("monitorModule", () => {
});
});
describe("addDemoMonitors", () => {
it("should add demo monitors successfully", async () => {
describe("addDemoMonitors", function() {
it("should add demo monitors successfully", async function() {
// Arrange
const userId = "user123";
const teamId = "team123";
@@ -1887,7 +1912,7 @@ describe("monitorModule", () => {
expect(result).to.deep.equal([{ _id: "123" }]);
});
it("should handle database errors", async () => {
it("should handle database errors", async function() {
const userId = "user123";
const teamId = "team123";
+15 -12
View File
@@ -6,31 +6,32 @@ import {
deleteNotificationsByMonitorId,
} from "../../db/mongo/modules/notificationModule.js";
describe("notificationModule", () => {
describe("notificationModule", function() {
const mockNotification = {
monitorId: "123",
};
const mockNotifications = [mockNotification];
let notificationSaveStub, notificationFindStub, notificationDeleteManyStub;
beforeEach(() => {
beforeEach(function() {
notificationSaveStub = sinon.stub(Notification.prototype, "save").resolves();
notificationFindStub = sinon.stub(Notification, "find").resolves();
notificationDeleteManyStub = sinon.stub(Notification, "deleteMany").resolves();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("createNotification", () => {
it("should create a new notification", async () => {
describe("createNotification", function() {
it("should create a new notification", async function() {
const notificationData = { _id: "123", name: "test" };
notificationSaveStub.resolves(notificationData);
const res = await createNotification(notificationData);
expect(res).to.deep.equal(notificationData);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
notificationSaveStub.rejects(err);
try {
@@ -41,13 +42,14 @@ describe("notificationModule", () => {
});
});
describe("getNotificationsByMonitorId", () => {
it("should return notifications by monitor ID", async () => {
describe("getNotificationsByMonitorId", function() {
it("should return notifications by monitor ID", async function() {
notificationFindStub.resolves(mockNotifications);
const res = await getNotificationsByMonitorId(mockNotification.monitorId);
expect(res).to.deep.equal(mockNotifications);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
notificationFindStub.rejects(err);
try {
@@ -58,13 +60,14 @@ describe("notificationModule", () => {
});
});
describe("deleteNotificationsByMonitorId", () => {
it("should delete notifications by monitor ID", async () => {
describe("deleteNotificationsByMonitorId", function() {
it("should delete notifications by monitor ID", async function() {
notificationDeleteManyStub.resolves({ deletedCount: mockNotifications.length });
const res = await deleteNotificationsByMonitorId(mockNotification.monitorId);
expect(res).to.deep.equal(mockNotifications.length);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
notificationDeleteManyStub.rejects(err);
try {
+10 -9
View File
@@ -14,25 +14,26 @@ const mockPageSpeedCheck = {
const mockDeletedResult = { deletedCount: 1 };
describe("pageSpeedCheckModule", () => {
describe("pageSpeedCheckModule", function() {
let pageSpeedCheckSaveStub, pageSpeedCheckDeleteManyStub;
beforeEach(() => {
beforeEach(function() {
pageSpeedCheckSaveStub = sinon.stub(PageSpeedCheck.prototype, "save");
pageSpeedCheckDeleteManyStub = sinon.stub(PageSpeedCheck, "deleteMany");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("createPageSpeedCheck", () => {
it("should return a page speed check", async () => {
describe("createPageSpeedCheck", function() {
it("should return a page speed check", async function() {
pageSpeedCheckSaveStub.resolves(mockPageSpeedCheck);
const pageSpeedCheck = await createPageSpeedCheck(mockPageSpeedCheck);
expect(pageSpeedCheck).to.deep.equal(mockPageSpeedCheck);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
pageSpeedCheckSaveStub.rejects(err);
try {
@@ -44,14 +45,14 @@ describe("pageSpeedCheckModule", () => {
});
});
describe("deletePageSpeedChecksByMonitorId", () => {
it("should return the number of deleted checks", async () => {
describe("deletePageSpeedChecksByMonitorId", function() {
it("should return the number of deleted checks", async function() {
pageSpeedCheckDeleteManyStub.resolves(mockDeletedResult);
const result = await deletePageSpeedChecksByMonitorId("monitorId");
expect(result).to.deep.equal(mockDeletedResult.deletedCount);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
pageSpeedCheckDeleteManyStub.rejects(err);
try {
+25 -17
View File
@@ -43,7 +43,7 @@ const createQueryChain = (finalResult, comparePasswordResult = false) => ({
save: sinon.stub().resolves(),
});
describe("recoveryModule", () => {
describe("recoveryModule", function() {
let deleteManyStub,
saveStub,
findOneStub,
@@ -51,7 +51,8 @@ describe("recoveryModule", () => {
userSaveStub,
userFindOneStub;
let req, res;
beforeEach(() => {
beforeEach(function() {
req = {
body: { email: "test@test.com" },
};
@@ -63,18 +64,19 @@ describe("recoveryModule", () => {
userFindOneStub = sinon.stub().resolves();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("requestRecoveryToken", () => {
it("should return a recovery token", async () => {
describe("requestRecoveryToken", function() {
it("should return a recovery token", async function() {
deleteManyStub.resolves();
saveStub.resolves(mockRecoveryToken);
const result = await requestRecoveryToken(req, res);
expect(result.email).to.equal(mockRecoveryToken.email);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("Test error");
deleteManyStub.rejects(err);
try {
@@ -85,13 +87,15 @@ describe("recoveryModule", () => {
}
});
});
describe("validateRecoveryToken", () => {
it("should return a recovery token if found", async () => {
describe("validateRecoveryToken", function() {
it("should return a recovery token if found", async function() {
findOneStub.resolves(mockRecoveryToken);
const result = await validateRecoveryToken(req, res);
expect(result).to.deep.equal(mockRecoveryToken);
});
it("should thrown an error if a token is not found", async () => {
it("should thrown an error if a token is not found", async function() {
findOneStub.resolves(null);
try {
await validateRecoveryToken(req, res);
@@ -100,7 +104,8 @@ describe("recoveryModule", () => {
expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND);
}
});
it("should handle DB errors", async () => {
it("should handle DB errors", async function() {
const err = new Error("Test error");
findOneStub.rejects(err);
try {
@@ -112,19 +117,19 @@ describe("recoveryModule", () => {
});
});
describe("resetPassword", () => {
beforeEach(() => {
describe("resetPassword", function() {
beforeEach(function() {
req.body = {
password: "test",
newPassword: "test1",
};
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should thrown an error if a recovery token is not found", async () => {
it("should thrown an error if a recovery token is not found", async function() {
findOneStub.resolves(null);
try {
await resetPassword(req, res);
@@ -133,7 +138,8 @@ describe("recoveryModule", () => {
expect(error.message).to.equal(errorMessages.DB_TOKEN_NOT_FOUND);
}
});
it("should throw an error if a user is not found", async () => {
it("should throw an error if a user is not found", async function() {
findOneStub.resolves(mockRecoveryToken);
userFindOneStub = sinon.stub(User, "findOne").resolves(null);
try {
@@ -143,7 +149,8 @@ describe("recoveryModule", () => {
expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND);
}
});
it("should throw an error if the passwords match", async () => {
it("should throw an error if the passwords match", async function() {
findOneStub.resolves(mockRecoveryToken);
saveStub.resolves();
userFindOneStub = sinon
@@ -156,7 +163,8 @@ describe("recoveryModule", () => {
expect(error.message).to.equal(errorMessages.DB_RESET_PASSWORD_BAD_MATCH);
}
});
it("should return a user without password if successful", async () => {
it("should return a user without password if successful", async function() {
findOneStub.resolves(mockRecoveryToken);
saveStub.resolves();
userFindOneStub = sinon
+14 -9
View File
@@ -9,23 +9,26 @@ const mockAppSettings = {
appName: "Test App",
};
describe("SettingsModule", () => {
describe("SettingsModule", function() {
let appSettingsFindOneStub, appSettingsFindOneAndUpdateStub;
beforeEach(() => {
beforeEach(function() {
appSettingsFindOneStub = sinon.stub(AppSettings, "findOne");
appSettingsFindOneAndUpdateStub = sinon.stub(AppSettings, "findOneAndUpdate");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("getAppSettings", () => {
it("should return app settings", async () => {
describe("getAppSettings", function() {
it("should return app settings", async function() {
appSettingsFindOneStub.resolves(mockAppSettings);
const result = await getAppSettings();
expect(result).to.deep.equal(mockAppSettings);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("Test error");
appSettingsFindOneStub.throws(err);
try {
@@ -35,13 +38,15 @@ describe("SettingsModule", () => {
}
});
});
describe("updateAppSettings", () => {
it("should update app settings", async () => {
describe("updateAppSettings", function() {
it("should update app settings", async function() {
appSettingsFindOneAndUpdateStub.resolves(mockAppSettings);
const result = await updateAppSettings(mockAppSettings);
expect(result).to.deep.equal(mockAppSettings);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("Test error");
appSettingsFindOneAndUpdateStub.throws(err);
try {
+16 -10
View File
@@ -6,18 +6,21 @@ import {
import StatusPage from "../../db/models/StatusPage.js";
import { errorMessages } from "../../utils/messages.js";
describe("statusPageModule", () => {
describe("statusPageModule", function() {
let statusPageFindOneStub, statusPageSaveStub, statusPageFindStub;
beforeEach(() => {
beforeEach(function() {
statusPageSaveStub = sinon.stub(StatusPage.prototype, "save");
statusPageFindOneStub = sinon.stub(StatusPage, "findOne");
statusPageFindStub = sinon.stub(StatusPage, "find");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("createStatusPage", () => {
it("should throw an error if a non-unique url is provided", async () => {
describe("createStatusPage", function() {
it("should throw an error if a non-unique url is provided", async function() {
statusPageFindOneStub.resolves(true);
try {
await createStatusPage({ url: "test" });
@@ -26,7 +29,8 @@ describe("statusPageModule", () => {
expect(error.message).to.equal(errorMessages.STATUS_PAGE_URL_NOT_UNIQUE);
}
});
it("should handle duplicate URL errors", async () => {
it("should handle duplicate URL errors", async function() {
const err = new Error("test");
err.code = 11000;
statusPageSaveStub.rejects(err);
@@ -36,7 +40,8 @@ describe("statusPageModule", () => {
expect(error).to.deep.equal(err);
}
});
it("should return a status page if a unique url is provided", async () => {
it("should return a status page if a unique url is provided", async function() {
statusPageFindOneStub.resolves(null);
statusPageFindStub.resolves([]);
const mockStatusPage = { url: "test" };
@@ -45,8 +50,9 @@ describe("statusPageModule", () => {
expect(statusPage.url).to.equal(mockStatusPage.url);
});
});
describe("getStatusPageByUrl", () => {
it("should throw an error if a status page is not found", async () => {
describe("getStatusPageByUrl", function() {
it("should throw an error if a status page is not found", async function() {
statusPageFindOneStub.resolves(null);
try {
await getStatusPageByUrl("test");
@@ -56,7 +62,7 @@ describe("statusPageModule", () => {
}
});
it("should return a status page if a status page is found", async () => {
it("should return a status page if a status page is found", async function() {
const mockStatusPage = { url: "test" };
statusPageFindOneStub.resolves(mockStatusPage);
const statusPage = await getStatusPageByUrl(mockStatusPage.url);
+52 -34
View File
@@ -27,7 +27,7 @@ const imageFile = {
image: 1,
};
describe("userModule", () => {
describe("userModule", function() {
let teamSaveStub,
teamFindByIdAndDeleteStub,
userSaveStub,
@@ -39,7 +39,8 @@ describe("userModule", () => {
userUpdateOneStub,
generateAvatarImageStub,
parseBooleanStub;
beforeEach(() => {
beforeEach(function() {
teamSaveStub = sinon.stub(TeamModel.prototype, "save");
teamFindByIdAndDeleteStub = sinon.stub(TeamModel, "findByIdAndDelete");
userSaveStub = sinon.stub(UserModel.prototype, "save");
@@ -53,12 +54,12 @@ describe("userModule", () => {
parseBooleanStub = sinon.stub().returns(true);
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("insertUser", () => {
it("should insert a regular user", async () => {
describe("insertUser", function() {
it("should insert a regular user", async function() {
userSaveStub.resolves(mockUser);
userFindOneStub.returns({
select: sinon.stub().returns({
@@ -68,7 +69,8 @@ describe("userModule", () => {
const result = await insertUser(mockUser, imageFile, generateAvatarImageStub);
expect(result).to.deep.equal(mockUser);
});
it("should insert a superadmin user", async () => {
it("should insert a superadmin user", async function() {
userSaveStub.resolves(mockSuperUser);
userFindOneStub.returns({
select: sinon.stub().returns({
@@ -78,7 +80,8 @@ describe("userModule", () => {
const result = await insertUser(mockSuperUser, imageFile, generateAvatarImageStub);
expect(result).to.deep.equal(mockSuperUser);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
userSaveStub.rejects(err);
try {
@@ -88,7 +91,8 @@ describe("userModule", () => {
expect(error).to.deep.equal(err);
}
});
it("should handle a duplicate key error", async () => {
it("should handle a duplicate key error", async function() {
const err = new Error("test error");
err.code = 11000;
userSaveStub.rejects(err);
@@ -100,8 +104,9 @@ describe("userModule", () => {
}
});
});
describe("getUserByEmail", () => {
it("should return a user", async () => {
describe("getUserByEmail", function() {
it("should return a user", async function() {
userFindOneStub.returns({
select: sinon.stub().resolves(mockUser),
});
@@ -109,8 +114,9 @@ describe("userModule", () => {
expect(result).to.deep.equal(mockUser);
});
});
describe("getUserByEmail", () => {
it("throw an error if a user is not found", async () => {
describe("getUserByEmail", function() {
it("throw an error if a user is not found", async function() {
userFindOneStub.returns({
select: sinon.stub().resolves(null),
});
@@ -121,9 +127,11 @@ describe("userModule", () => {
}
});
});
describe("updateUser", () => {
describe("updateUser", function() {
let req, res;
beforeEach(() => {
beforeEach(function() {
req = {
params: {
userId: "testId",
@@ -140,8 +148,9 @@ describe("userModule", () => {
res = {};
});
afterEach(() => {});
it("should update a user", async () => {
afterEach(function() {});
it("should update a user", async function() {
parseBooleanStub.returns(false);
userFindByIdAndUpdateStub.returns({
select: sinon.stub().returns({
@@ -156,7 +165,8 @@ describe("userModule", () => {
);
expect(result).to.deep.equal(mockUser);
});
it("should delete a user profile image", async () => {
it("should delete a user profile image", async function() {
req.body.deleteProfileImage = "true";
userFindByIdAndUpdateStub.returns({
select: sinon.stub().returns({
@@ -171,7 +181,8 @@ describe("userModule", () => {
);
expect(result).to.deep.equal(mockUser);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
userFindByIdAndUpdateStub.throws(err);
try {
@@ -182,13 +193,15 @@ describe("userModule", () => {
}
});
});
describe("deleteUser", async () => {
it("should return a deleted user", async () => {
describe("deleteUser", function() {
it("should return a deleted user", async function() {
userFindByIdAndDeleteStub.resolves(mockUser);
const result = await deleteUser("testId");
expect(result).to.deep.equal(mockUser);
});
it("should throw an error if a user is not found", async () => {
it("should throw an error if a user is not found", async function() {
try {
await deleteUser("testId");
} catch (error) {
@@ -196,7 +209,8 @@ describe("userModule", () => {
expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND);
}
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
userFindByIdAndDeleteStub.throws(err);
try {
@@ -208,13 +222,14 @@ describe("userModule", () => {
});
});
describe("deleteTeam", () => {
it("should return true if team deleted", async () => {
describe("deleteTeam", function() {
it("should return true if team deleted", async function() {
teamFindByIdAndDeleteStub.resolves();
const result = await deleteTeam("testId");
expect(result).to.equal(true);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
teamFindByIdAndDeleteStub.throws(err);
try {
@@ -226,13 +241,14 @@ describe("userModule", () => {
});
});
describe("deleteAllOtherUsers", () => {
it("should return true if all other users deleted", async () => {
describe("deleteAllOtherUsers", function() {
it("should return true if all other users deleted", async function() {
userDeleteManyStub.resolves(true);
const result = await deleteAllOtherUsers();
expect(result).to.equal(true);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
userDeleteManyStub.throws(err);
try {
@@ -244,8 +260,8 @@ describe("userModule", () => {
});
});
describe("getAllUsers", () => {
it("should return all users", async () => {
describe("getAllUsers", function() {
it("should return all users", async function() {
userFindStub.returns({
select: sinon.stub().returns({
select: sinon.stub().resolves([mockUser]),
@@ -254,7 +270,8 @@ describe("userModule", () => {
const result = await getAllUsers();
expect(result).to.deep.equal([mockUser]);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
userFindStub.throws(err);
try {
@@ -266,13 +283,14 @@ describe("userModule", () => {
});
});
describe("logoutUser", async () => {
it("should return true if user logged out", async () => {
describe("logoutUser", function() {
it("should return true if user logged out", async function() {
userUpdateOneStub.resolves(true);
const result = await logoutUser("testId");
expect(result).to.equal(true);
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const err = new Error("test error");
userUpdateOneStub.throws(err);
try {
+14 -13
View File
@@ -1,7 +1,7 @@
import sinon from "sinon";
import EmailService from "../../service/emailService.js";
describe("EmailService - Constructor", () => {
describe("EmailService - Constructor", function() {
let settingsServiceMock;
let fsMock;
let pathMock;
@@ -10,7 +10,7 @@ describe("EmailService - Constructor", () => {
let nodemailerMock;
let loggerMock;
beforeEach(() => {
beforeEach(function() {
settingsServiceMock = {
getSettings: sinon.stub().returns({
systemEmailHost: "smtp.example.com",
@@ -43,11 +43,11 @@ describe("EmailService - Constructor", () => {
};
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should initialize template loaders and email transporter", () => {
it("should initialize template loaders and email transporter", function() {
const emailService = new EmailService(
settingsServiceMock,
fsMock,
@@ -79,7 +79,7 @@ describe("EmailService - Constructor", () => {
});
});
it("should have undefined templates if FS fails", () => {
it("should have undefined templates if FS fails", function() {
fsMock = {
readFileSync: sinon.stub().throws(new Error("File read error")),
};
@@ -97,7 +97,7 @@ describe("EmailService - Constructor", () => {
});
});
describe("EmailService - buildAndSendEmail", () => {
describe("EmailService - buildAndSendEmail", function() {
let settingsServiceMock;
let fsMock;
let pathMock;
@@ -106,7 +106,8 @@ describe("EmailService - buildAndSendEmail", () => {
let nodemailerMock;
let loggerMock;
let emailService;
beforeEach(() => {
beforeEach(function() {
settingsServiceMock = {
getSettings: sinon.stub().returns({
systemEmailHost: "smtp.example.com",
@@ -149,11 +150,11 @@ describe("EmailService - buildAndSendEmail", () => {
);
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should build and send email successfully", async () => {
it("should build and send email successfully", async function() {
const messageId = await emailService.buildAndSendEmail(
"welcomeEmailTemplate",
{},
@@ -165,7 +166,7 @@ describe("EmailService - buildAndSendEmail", () => {
expect(nodemailerMock.createTransport().sendMail.calledOnce).to.be.true;
});
it("should log error if building HTML fails", async () => {
it("should log error if building HTML fails", async function() {
mjml2htmlMock.throws(new Error("MJML error"));
const messageId = await emailService.buildAndSendEmail(
@@ -178,7 +179,7 @@ describe("EmailService - buildAndSendEmail", () => {
expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error");
});
it("should log error if sending email fails", async () => {
it("should log error if sending email fails", async function() {
nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error"));
await emailService.buildAndSendEmail(
"welcomeEmailTemplate",
@@ -190,7 +191,7 @@ describe("EmailService - buildAndSendEmail", () => {
expect(loggerMock.error.getCall(0).args[0].message).to.equal("SMTP error");
});
it("should log error if both building HTML and sending email fail", async () => {
it("should log error if both building HTML and sending email fail", async function() {
mjml2htmlMock.throws(new Error("MJML error"));
nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error"));
@@ -207,5 +208,5 @@ describe("EmailService - buildAndSendEmail", () => {
expect(loggerMock.error.getCall(1).args[0].message).to.equal("SMTP error");
});
it("should log an error if buildHtml fails", async () => {});
it("should log an error if buildHtml fails", async function() {});
});
+79 -56
View File
@@ -51,7 +51,7 @@ class WorkerStub {
}
}
describe("JobQueue", () => {
describe("JobQueue", function() {
let settingsService,
logger,
db,
@@ -60,7 +60,7 @@ describe("JobQueue", () => {
notificationService,
jobQueue;
beforeEach(async () => {
beforeEach(async function() {
settingsService = { getSettings: sinon.stub() };
statusService = { updateStatus: sinon.stub() };
notificationService = { handleNotifications: sinon.stub() };
@@ -83,12 +83,12 @@ describe("JobQueue", () => {
);
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("createJobQueue", () => {
it("should create a new JobQueue and add jobs for active monitors", async () => {
describe("createJobQueue", function() {
it("should create a new JobQueue and add jobs for active monitors", async function() {
db.getAllMonitors.returns([
{ id: 1, isActive: true },
{ id: 2, isActive: true },
@@ -108,7 +108,7 @@ describe("JobQueue", () => {
expect(jobQueue.queue.jobs.length).to.equal(4);
});
it("should reject with an error if an error occurs", async () => {
it("should reject with an error if an error occurs", async function() {
db.getAllMonitors.throws("Error");
try {
const jobQueue = await JobQueue.createJobQueue(
@@ -127,7 +127,7 @@ describe("JobQueue", () => {
}
});
it("should reject with an error if an error occurs, should not overwrite error data", async () => {
it("should reject with an error if an error occurs, should not overwrite error data", async function() {
const error = new Error("Error");
error.service = "otherService";
error.method = "otherMethod";
@@ -151,14 +151,15 @@ describe("JobQueue", () => {
});
});
describe("Constructor", () => {
it("should construct a new JobQueue with default port and host if not provided", async () => {
describe("Constructor", function() {
it("should construct a new JobQueue with default port and host if not provided", async function() {
settingsService.getSettings.returns({});
expect(jobQueue.connection.host).to.equal("127.0.0.1");
expect(jobQueue.connection.port).to.equal(6379);
});
it("should construct a new JobQueue with provided port and host", async () => {
it("should construct a new JobQueue with provided port and host", async function() {
settingsService.getSettings.returns({ redisHost: "localhost", redisPort: 1234 });
const jobQueue = await JobQueue.createJobQueue(
db,
@@ -175,8 +176,8 @@ describe("JobQueue", () => {
});
});
describe("isMaintenanceWindow", () => {
it("should throw an error if error occurs", async () => {
describe("isMaintenanceWindow", function() {
it("should throw an error if error occurs", async function() {
db.getMaintenanceWindowsByMonitorId.throws("Error");
const jobQueue = await JobQueue.createJobQueue(
db,
@@ -196,7 +197,7 @@ describe("JobQueue", () => {
}
});
it("should return true if in maintenance window with no repeat", async () => {
it("should return true if in maintenance window with no repeat", async function() {
db.getMaintenanceWindowsByMonitorId.returns([
{
active: true,
@@ -219,7 +220,7 @@ describe("JobQueue", () => {
expect(inWindow).to.be.true;
});
it("should return true if in maintenance window with repeat", async () => {
it("should return true if in maintenance window with repeat", async function() {
db.getMaintenanceWindowsByMonitorId.returns([
{
active: true,
@@ -242,7 +243,7 @@ describe("JobQueue", () => {
expect(inWindow).to.be.true;
});
it("should return false if in end < start", async () => {
it("should return false if in end < start", async function() {
db.getMaintenanceWindowsByMonitorId.returns([
{
active: true,
@@ -264,7 +265,8 @@ describe("JobQueue", () => {
const inWindow = await jobQueue.isInMaintenanceWindow(1);
expect(inWindow).to.be.false;
});
it("should return false if not in maintenance window", async () => {
it("should return false if not in maintenance window", async function() {
db.getMaintenanceWindowsByMonitorId.returns([
{
active: false,
@@ -288,8 +290,8 @@ describe("JobQueue", () => {
});
});
describe("createJobHandler", () => {
it("resolve to an error if an error is thrown within", async () => {
describe("createJobHandler", function() {
it("resolve to an error if an error is thrown within", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -310,7 +312,7 @@ describe("JobQueue", () => {
}
});
it("should log info if job is in maintenance window", async () => {
it("should log info if job is in maintenance window", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -330,7 +332,7 @@ describe("JobQueue", () => {
);
});
it("should return if status has not changed", async () => {
it("should return if status has not changed", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -348,7 +350,7 @@ describe("JobQueue", () => {
expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true;
});
it("should return if status has changed, but prevStatus was undefined (monitor paused)", async () => {
it("should return if status has changed, but prevStatus was undefined (monitor paused)", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -367,7 +369,8 @@ describe("JobQueue", () => {
await handler({ data: { _id: 1 } });
expect(jobQueue.notificationService.handleNotifications.notCalled).to.be.true;
});
it("should call notification service if status changed and monitor was not paused", async () => {
it("should call notification service if status changed and monitor was not paused", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -388,8 +391,8 @@ describe("JobQueue", () => {
});
});
describe("getWorkerStats", () => {
it("should throw an error if getRepeatable Jobs fails", async () => {
describe("getWorkerStats", function() {
it("should throw an error if getRepeatable Jobs fails", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -410,7 +413,8 @@ describe("JobQueue", () => {
expect(error.method).to.equal("getWorkerStats");
}
});
it("should throw an error if getRepeatable Jobs fails but respect existing error data", async () => {
it("should throw an error if getRepeatable Jobs fails but respect existing error data", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -436,8 +440,8 @@ describe("JobQueue", () => {
});
});
describe("scaleWorkers", () => {
it("should scale workers to 5 if no workers", async () => {
describe("scaleWorkers", function() {
it("should scale workers to 5 if no workers", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -450,7 +454,8 @@ describe("JobQueue", () => {
);
expect(jobQueue.workers.length).to.equal(5);
});
it("should scale workers up", async () => {
it("should scale workers up", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -467,7 +472,8 @@ describe("JobQueue", () => {
});
expect(jobQueue.workers.length).to.equal(20);
});
it("should scale workers down, even with error of worker.close fails", async () => {
it("should scale workers down, even with error of worker.close fails", async function() {
WorkerStub.prototype.close = async () => {
throw new Error("Error");
};
@@ -492,7 +498,8 @@ describe("JobQueue", () => {
});
expect(jobQueue.workers.length).to.equal(5);
});
it("should scale workers down", async () => {
it("should scale workers down", async function() {
WorkerStub.prototype.close = async () => {
return true;
};
@@ -517,7 +524,8 @@ describe("JobQueue", () => {
});
expect(jobQueue.workers.length).to.equal(5);
});
it("should return false if scaling doesn't happen", async () => {
it("should return false if scaling doesn't happen", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -534,8 +542,8 @@ describe("JobQueue", () => {
});
});
describe("getJobs", () => {
it("should return jobs", async () => {
describe("getJobs", function() {
it("should return jobs", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -549,7 +557,8 @@ describe("JobQueue", () => {
const jobs = await jobQueue.getJobs();
expect(jobs.length).to.equal(0);
});
it("should throw an error if getRepeatableJobs fails", async () => {
it("should throw an error if getRepeatableJobs fails", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -571,7 +580,8 @@ describe("JobQueue", () => {
expect(error.method).to.equal("getJobs");
}
});
it("should throw an error if getRepeatableJobs fails but respect existing error data", async () => {
it("should throw an error if getRepeatableJobs fails but respect existing error data", async function() {
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
@@ -598,12 +608,13 @@ describe("JobQueue", () => {
});
});
describe("getJobStats", () => {
it("should return job stats for no jobs", async () => {
describe("getJobStats", function() {
it("should return job stats for no jobs", async function() {
const jobStats = await jobQueue.getJobStats();
expect(jobStats).to.deep.equal({ jobs: [], workers: 5 });
});
it("should return job stats for jobs", async () => {
it("should return job stats for jobs", async function() {
jobQueue.queue.getJobs = async () => {
return [{ data: { url: "test" }, getState: async () => "completed" }];
};
@@ -613,7 +624,8 @@ describe("JobQueue", () => {
workers: 5,
});
});
it("should reject with an error if mapping jobs fails", async () => {
it("should reject with an error if mapping jobs fails", async function() {
jobQueue.queue.getJobs = async () => {
return [
{
@@ -632,7 +644,8 @@ describe("JobQueue", () => {
expect(error.method).to.equal("getJobStats");
}
});
it("should reject with an error if mapping jobs fails but respect existing error data", async () => {
it("should reject with an error if mapping jobs fails but respect existing error data", async function() {
jobQueue.queue.getJobs = async () => {
return [
{
@@ -656,12 +669,13 @@ describe("JobQueue", () => {
});
});
describe("addJob", () => {
it("should add a job to the queue", async () => {
describe("addJob", function() {
it("should add a job to the queue", async function() {
jobQueue.addJob("test", { url: "test" });
expect(jobQueue.queue.jobs.length).to.equal(1);
});
it("should reject with an error if adding fails", async () => {
it("should reject with an error if adding fails", async function() {
jobQueue.queue.add = async () => {
throw new Error("Error adding job");
};
@@ -673,7 +687,8 @@ describe("JobQueue", () => {
expect(error.method).to.equal("addJob");
}
});
it("should reject with an error if adding fails but respect existing error data", async () => {
it("should reject with an error if adding fails but respect existing error data", async function() {
jobQueue.queue.add = async () => {
const error = new Error("Error adding job");
error.service = "otherService";
@@ -689,8 +704,9 @@ describe("JobQueue", () => {
}
});
});
describe("deleteJob", () => {
it("should delete a job from the queue", async () => {
describe("deleteJob", function() {
it("should delete a job from the queue", async function() {
jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] });
jobQueue.scaleWorkers = sinon.stub();
const monitor = { _id: 1 };
@@ -702,7 +718,8 @@ describe("JobQueue", () => {
// expect(jobQueue.getWorkerStats.calledOnce).to.be.true;
// expect(jobQueue.scaleWorkers.calledOnce).to.be.true;
});
it("should log an error if job is not found", async () => {
it("should log an error if job is not found", async function() {
jobQueue.getWorkerStats = sinon.stub().returns({ load: 1, jobs: [{}] });
jobQueue.scaleWorkers = sinon.stub();
const monitor = { _id: 1 };
@@ -711,7 +728,8 @@ describe("JobQueue", () => {
await jobQueue.deleteJob({ id_: 2 });
expect(logger.error.calledOnce).to.be.true;
});
it("should reject with an error if removeRepeatable fails", async () => {
it("should reject with an error if removeRepeatable fails", async function() {
jobQueue.queue.removeRepeatable = async () => {
const error = new Error("removeRepeatable error");
throw error;
@@ -725,7 +743,8 @@ describe("JobQueue", () => {
expect(error.method).to.equal("deleteJob");
}
});
it("should reject with an error if removeRepeatable fails but respect existing error data", async () => {
it("should reject with an error if removeRepeatable fails but respect existing error data", async function() {
jobQueue.queue.removeRepeatable = async () => {
const error = new Error("removeRepeatable error");
error.service = "otherService";
@@ -742,8 +761,9 @@ describe("JobQueue", () => {
}
});
});
describe("getMetrics", () => {
it("should return metrics for the job queue", async () => {
describe("getMetrics", function() {
it("should return metrics for the job queue", async function() {
jobQueue.queue.getWaitingCount = async () => 1;
jobQueue.queue.getActiveCount = async () => 2;
jobQueue.queue.getCompletedCount = async () => 3;
@@ -760,7 +780,8 @@ describe("JobQueue", () => {
repeatableJobs: 3,
});
});
it("should log an error if metrics operations fail", async () => {
it("should log an error if metrics operations fail", async function() {
jobQueue.queue.getWaitingCount = async () => {
throw new Error("Error");
};
@@ -770,8 +791,8 @@ describe("JobQueue", () => {
});
});
describe("obliterate", () => {
it("should return true if obliteration is successful", async () => {
describe("obliterate", function() {
it("should return true if obliteration is successful", async function() {
jobQueue.queue.pause = async () => true;
jobQueue.getJobs = async () => [{ key: 1, id: 1 }];
jobQueue.queue.removeRepeatableByKey = async () => true;
@@ -780,7 +801,8 @@ describe("JobQueue", () => {
const obliteration = await jobQueue.obliterate();
expect(obliteration).to.be.true;
});
it("should throw an error if obliteration fails", async () => {
it("should throw an error if obliteration fails", async function() {
jobQueue.getMetrics = async () => {
throw new Error("Error");
};
@@ -792,7 +814,8 @@ describe("JobQueue", () => {
expect(error.method).to.equal("obliterate");
}
});
it("should throw an error if obliteration fails but respect existing error data", async () => {
it("should throw an error if obliteration fails but respect existing error data", async function() {
jobQueue.getMetrics = async () => {
const error = new Error("Error");
error.service = "otherService";
+70 -45
View File
@@ -3,10 +3,10 @@ import NetworkService from "../../service/networkService.js";
import { expect } from "chai";
import http from "http";
import { errorMessages } from "../../utils/messages.js";
describe("Network Service", () => {
describe("Network Service", function() {
let axios, ping, Docker, logger, networkService;
beforeEach(() => {
beforeEach(function() {
axios = {
get: sinon.stub().resolves({
data: { foo: "bar" },
@@ -34,21 +34,23 @@ describe("Network Service", () => {
logger = { error: sinon.stub() };
networkService = new NetworkService(axios, ping, logger, http, Docker);
});
describe("constructor", () => {
it("should create a new NetworkService instance", () => {
describe("constructor", function() {
it("should create a new NetworkService instance", function() {
const networkService = new NetworkService();
expect(networkService).to.be.an.instanceOf(NetworkService);
});
});
describe("timeRequest", () => {
it("should time an asynchronous operation", async () => {
describe("timeRequest", function() {
it("should time an asynchronous operation", async function() {
const operation = sinon.stub().resolves("success");
const { response, responseTime } = await networkService.timeRequest(operation);
expect(response).to.equal("success");
expect(responseTime).to.be.a("number");
});
it("should handle errors if operation throws error", async () => {
it("should handle errors if operation throws error", async function() {
const error = new Error("Test error");
const operation = sinon.stub().throws(error);
const { response, responseTime } = await networkService.timeRequest(operation);
@@ -58,8 +60,8 @@ describe("Network Service", () => {
});
});
describe("requestPing", () => {
it("should return a response object if ping successful", async () => {
describe("requestPing", function() {
it("should return a response object if ping successful", async function() {
const pingResult = await networkService.requestPing({
data: { url: "http://test.com", _id: "123" },
});
@@ -68,7 +70,8 @@ describe("Network Service", () => {
expect(pingResult.responseTime).to.be.a("number");
expect(pingResult.status).to.be.true;
});
it("should return a response object if ping unsuccessful", async () => {
it("should return a response object if ping unsuccessful", async function() {
const error = new Error("Test error");
networkService.timeRequest = sinon
.stub()
@@ -82,7 +85,8 @@ describe("Network Service", () => {
expect(pingResult.status).to.be.false;
expect(pingResult.code).to.equal(networkService.PING_ERROR);
});
it("should throw an error if ping cannot resolve", async () => {
it("should throw an error if ping cannot resolve", async function() {
const error = new Error("test error");
networkService.timeRequest = sinon.stub().throws(error);
try {
@@ -95,8 +99,9 @@ describe("Network Service", () => {
}
});
});
describe("requestHttp", () => {
it("should return a response object if http successful", async () => {
describe("requestHttp", function() {
it("should return a response object if http successful", async function() {
const job = { data: { url: "http://test.com", _id: "123", type: "http" } };
const httpResult = await networkService.requestHttp(job);
expect(httpResult.monitorId).to.equal("123");
@@ -104,7 +109,8 @@ describe("Network Service", () => {
expect(httpResult.responseTime).to.be.a("number");
expect(httpResult.status).to.be.true;
});
it("should return a response object if http unsuccessful", async () => {
it("should return a response object if http unsuccessful", async function() {
const error = new Error("Test error");
error.response = { status: 404 };
networkService.timeRequest = sinon
@@ -118,7 +124,8 @@ describe("Network Service", () => {
expect(httpResult.status).to.be.false;
expect(httpResult.code).to.equal(404);
});
it("should return a response object if http unsuccessful with unknown code", async () => {
it("should return a response object if http unsuccessful with unknown code", async function() {
const error = new Error("Test error");
error.response = {};
networkService.timeRequest = sinon
@@ -132,7 +139,8 @@ describe("Network Service", () => {
expect(httpResult.status).to.be.false;
expect(httpResult.code).to.equal(networkService.NETWORK_ERROR);
});
it("should throw an error if an error occurs", async () => {
it("should throw an error if an error occurs", async function() {
const error = new Error("test error");
networkService.timeRequest = sinon.stub().throws(error);
try {
@@ -146,8 +154,8 @@ describe("Network Service", () => {
});
});
describe("requestPagespeed", () => {
it("should return a response object if pagespeed successful", async () => {
describe("requestPagespeed", function() {
it("should return a response object if pagespeed successful", async function() {
const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } };
const pagespeedResult = await networkService.requestPagespeed(job);
expect(pagespeedResult.monitorId).to.equal("123");
@@ -155,7 +163,8 @@ describe("Network Service", () => {
expect(pagespeedResult.responseTime).to.be.a("number");
expect(pagespeedResult.status).to.be.true;
});
it("should return a response object if pagespeed unsuccessful", async () => {
it("should return a response object if pagespeed unsuccessful", async function() {
const error = new Error("Test error");
error.response = { status: 404 };
networkService.timeRequest = sinon
@@ -169,7 +178,8 @@ describe("Network Service", () => {
expect(pagespeedResult.status).to.be.false;
expect(pagespeedResult.code).to.equal(404);
});
it("should return a response object if pagespeed unsuccessful with an unknown code", async () => {
it("should return a response object if pagespeed unsuccessful with an unknown code", async function() {
const error = new Error("Test error");
error.response = {};
networkService.timeRequest = sinon
@@ -183,7 +193,8 @@ describe("Network Service", () => {
expect(pagespeedResult.status).to.be.false;
expect(pagespeedResult.code).to.equal(networkService.NETWORK_ERROR);
});
it("should throw an error if pagespeed cannot resolve", async () => {
it("should throw an error if pagespeed cannot resolve", async function() {
const error = new Error("test error");
networkService.timeRequest = sinon.stub().throws(error);
try {
@@ -197,8 +208,8 @@ describe("Network Service", () => {
});
});
describe("requestHardware", () => {
it("should return a response object if hardware successful", async () => {
describe("requestHardware", function() {
it("should return a response object if hardware successful", async function() {
const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } };
const httpResult = await networkService.requestHardware(job);
expect(httpResult.monitorId).to.equal("123");
@@ -206,7 +217,8 @@ describe("Network Service", () => {
expect(httpResult.responseTime).to.be.a("number");
expect(httpResult.status).to.be.true;
});
it("should return a response object if hardware successful and job has a secret", async () => {
it("should return a response object if hardware successful and job has a secret", async function() {
const job = {
data: {
url: "http://test.com",
@@ -221,7 +233,8 @@ describe("Network Service", () => {
expect(httpResult.responseTime).to.be.a("number");
expect(httpResult.status).to.be.true;
});
it("should return a response object if hardware unsuccessful", async () => {
it("should return a response object if hardware unsuccessful", async function() {
const error = new Error("Test error");
error.response = { status: 404 };
networkService.timeRequest = sinon
@@ -235,7 +248,8 @@ describe("Network Service", () => {
expect(httpResult.status).to.be.false;
expect(httpResult.code).to.equal(404);
});
it("should return a response object if hardware unsuccessful with unknown code", async () => {
it("should return a response object if hardware unsuccessful with unknown code", async function() {
const error = new Error("Test error");
error.response = {};
networkService.timeRequest = sinon
@@ -249,7 +263,8 @@ describe("Network Service", () => {
expect(httpResult.status).to.be.false;
expect(httpResult.code).to.equal(networkService.NETWORK_ERROR);
});
it("should throw an error if hardware cannot resolve", async () => {
it("should throw an error if hardware cannot resolve", async function() {
const error = new Error("test error");
networkService.timeRequest = sinon.stub().throws(error);
try {
@@ -263,8 +278,8 @@ describe("Network Service", () => {
});
});
describe("requestDocker", () => {
it("should return a response object if docker successful", async () => {
describe("requestDocker", function() {
it("should return a response object if docker successful", async function() {
const job = { data: { url: "http://test.com", _id: "123", type: "docker" } };
const dockerResult = await networkService.requestDocker(job);
expect(dockerResult.monitorId).to.equal("123");
@@ -273,7 +288,7 @@ describe("Network Service", () => {
expect(dockerResult.status).to.be.true;
});
it("should return a response object with status false if container not running", async () => {
it("should return a response object with status false if container not running", async function() {
Docker = class {
listContainers = sinon.stub().resolves([
{
@@ -292,7 +307,7 @@ describe("Network Service", () => {
expect(dockerResult.code).to.equal(200);
});
it("should handle an error when fetching the container", async () => {
it("should handle an error when fetching the container", async function() {
Docker = class {
listContainers = sinon.stub().resolves([
{
@@ -311,7 +326,7 @@ describe("Network Service", () => {
expect(dockerResult.code).to.equal(networkService.NETWORK_ERROR);
});
it("should throw an error if operations fail", async () => {
it("should throw an error if operations fail", async function() {
Docker = class {
listContainers = sinon.stub().resolves([
{
@@ -329,7 +344,8 @@ describe("Network Service", () => {
expect(error.message).to.equal("test error");
}
});
it("should throw an error if no matching images found", async () => {
it("should throw an error if no matching images found", async function() {
Docker = class {
listContainers = sinon.stub().resolves([]);
getContainer = sinon.stub().throws(new Error("test error"));
@@ -344,8 +360,8 @@ describe("Network Service", () => {
});
});
describe("getStatus", () => {
beforeEach(() => {
describe("getStatus", function() {
beforeEach(function() {
networkService.requestPing = sinon.stub();
networkService.requestHttp = sinon.stub();
networkService.requestPagespeed = sinon.stub();
@@ -353,45 +369,51 @@ describe("Network Service", () => {
networkService.requestDocker = sinon.stub();
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should call requestPing if type is ping", async () => {
it("should call requestPing if type is ping", async function() {
await networkService.getStatus({ data: { type: "ping" } });
expect(networkService.requestPing.calledOnce).to.be.true;
expect(networkService.requestDocker.notCalled).to.be.true;
expect(networkService.requestHttp.notCalled).to.be.true;
expect(networkService.requestPagespeed.notCalled).to.be.true;
});
it("should call requestHttp if type is http", async () => {
it("should call requestHttp if type is http", async function() {
await networkService.getStatus({ data: { type: "http" } });
expect(networkService.requestPing.notCalled).to.be.true;
expect(networkService.requestDocker.notCalled).to.be.true;
expect(networkService.requestHttp.calledOnce).to.be.true;
expect(networkService.requestPagespeed.notCalled).to.be.true;
});
it("should call requestPagespeed if type is pagespeed", async () => {
it("should call requestPagespeed if type is pagespeed", async function() {
await networkService.getStatus({ data: { type: "pagespeed" } });
expect(networkService.requestPing.notCalled).to.be.true;
expect(networkService.requestDocker.notCalled).to.be.true;
expect(networkService.requestHttp.notCalled).to.be.true;
expect(networkService.requestPagespeed.calledOnce).to.be.true;
});
it("should call requestHardware if type is hardware", async () => {
it("should call requestHardware if type is hardware", async function() {
await networkService.getStatus({ data: { type: "hardware" } });
expect(networkService.requestHardware.calledOnce).to.be.true;
expect(networkService.requestDocker.notCalled).to.be.true;
expect(networkService.requestPing.notCalled).to.be.true;
expect(networkService.requestPagespeed.notCalled).to.be.true;
});
it("should call requestDocker if type is Docker", async () => {
it("should call requestDocker if type is Docker", async function() {
await networkService.getStatus({ data: { type: "docker" } });
expect(networkService.requestDocker.calledOnce).to.be.true;
expect(networkService.requestHardware.notCalled).to.be.true;
expect(networkService.requestPing.notCalled).to.be.true;
expect(networkService.requestPagespeed.notCalled).to.be.true;
});
it("should throw an error if an unknown type is provided", async () => {
it("should throw an error if an unknown type is provided", async function() {
try {
await networkService.getStatus({ data: { type: "unknown" } });
} catch (error) {
@@ -400,7 +422,8 @@ describe("Network Service", () => {
expect(error.message).to.equal("Unsupported type: unknown");
}
});
it("should throw an error if job type is undefined", async () => {
it("should throw an error if job type is undefined", async function() {
try {
await networkService.getStatus({ data: { type: undefined } });
} catch (error) {
@@ -409,7 +432,8 @@ describe("Network Service", () => {
expect(error.message).to.equal("Unsupported type: unknown");
}
});
it("should throw an error if job is empty", async () => {
it("should throw an error if job is empty", async function() {
try {
await networkService.getStatus({});
} catch (error) {
@@ -417,7 +441,8 @@ describe("Network Service", () => {
expect(error.message).to.equal("Unsupported type: unknown");
}
});
it("should throw an error if job is null", async () => {
it("should throw an error if job is null", async function() {
try {
await networkService.getStatus(null);
} catch (error) {
@@ -2,9 +2,10 @@ import sinon from "sinon";
import NotificationService from "../../service/notificationService.js";
import { expect } from "chai";
describe("NotificationService", () => {
describe("NotificationService", function() {
let emailService, db, logger, notificationService;
beforeEach(() => {
beforeEach(function() {
db = {
getNotificationsByMonitorId: sinon.stub(),
};
@@ -17,18 +18,19 @@ describe("NotificationService", () => {
notificationService = new NotificationService(emailService, db, logger);
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("constructor", () => {
it("should create a new instance of NotificationService", () => {
describe("constructor", function() {
it("should create a new instance of NotificationService", function() {
expect(notificationService).to.be.an.instanceOf(NotificationService);
});
});
describe("sendEmail", async () => {
it("should send an email notification with Up Template", async () => {
describe("sendEmail", function() {
it("should send an email notification with Up Template", async function() {
const networkResponse = {
monitor: {
name: "Test Monitor",
@@ -47,7 +49,8 @@ describe("NotificationService", () => {
)
);
});
it("should send an email notification with Down Template", async () => {
it("should send an email notification with Down Template", async function() {
const networkResponse = {
monitor: {
name: "Test Monitor",
@@ -60,7 +63,8 @@ describe("NotificationService", () => {
await notificationService.sendEmail(networkResponse, address);
expect(notificationService.emailService.buildAndSendEmail.calledOnce).to.be.true;
});
it("should send an email notification with Up Template", async () => {
it("should send an email notification with Up Template", async function() {
const networkResponse = {
monitor: {
name: "Test Monitor",
@@ -75,8 +79,8 @@ describe("NotificationService", () => {
});
});
describe("handleNotifications", async () => {
it("should handle notifications based on the network response", async () => {
describe("handleNotifications", function() {
it("should handle notifications based on the network response", async function() {
notificationService.sendEmail = sinon.stub();
const res = await notificationService.handleNotifications({
monitor: {
@@ -86,7 +90,8 @@ describe("NotificationService", () => {
});
expect(res).to.be.true;
});
it("should handle hardware notifications", async () => {
it("should handle hardware notifications", async function() {
notificationService.sendEmail = sinon.stub();
const res = await notificationService.handleNotifications({
monitor: {
@@ -97,7 +102,7 @@ describe("NotificationService", () => {
expect(res).to.be.true;
});
it("should handle an error when getting notifications", async () => {
it("should handle an error when getting notifications", async function() {
const testError = new Error("Test Error");
notificationService.db.getNotificationsByMonitorId.rejects(testError);
await notificationService.handleNotifications({ monitorId: "123" });
@@ -105,9 +110,10 @@ describe("NotificationService", () => {
});
});
describe("sendHardwareEmail", async () => {
describe("sendHardwareEmail", function() {
let networkResponse, address, alerts;
beforeEach(() => {
beforeEach(function() {
networkResponse = {
monitor: {
name: "Test Monitor",
@@ -120,10 +126,11 @@ describe("NotificationService", () => {
alerts = ["test"];
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should send an email notification with Hardware Template", async () => {
it("should send an email notification with Hardware Template", async function() {
emailService.buildAndSendEmail.resolves(true);
const res = await notificationService.sendHardwareEmail(
networkResponse,
@@ -132,7 +139,8 @@ describe("NotificationService", () => {
);
expect(res).to.be.true;
});
it("should return false if no alerts are provided", async () => {
it("should return false if no alerts are provided", async function() {
alerts = [];
emailService.buildAndSendEmail.resolves(true);
const res = await notificationService.sendHardwareEmail(
@@ -143,9 +151,11 @@ describe("NotificationService", () => {
expect(res).to.be.false;
});
});
describe("handleStatusNotifications", async () => {
describe("handleStatusNotifications", function() {
let networkResponse;
beforeEach(() => {
beforeEach(function() {
networkResponse = {
monitor: {
name: "Test Monitor",
@@ -157,28 +167,31 @@ describe("NotificationService", () => {
};
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
it("should handle status notifications", async () => {
it("should handle status notifications", async function() {
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 () => {
it("should return false if status hasn't changed", async function() {
networkResponse.statusChanged = false;
const res = await notificationService.handleStatusNotifications(networkResponse);
expect(res).to.be.false;
});
it("should return false if prevStatus is undefined", async () => {
it("should return false if prevStatus is undefined", async function() {
networkResponse.prevStatus = undefined;
const res = await notificationService.handleStatusNotifications(networkResponse);
expect(res).to.be.false;
});
it("should handle an error", async () => {
it("should handle an error", async function() {
const testError = new Error("Test Error");
db.getNotificationsByMonitorId.rejects(testError);
try {
@@ -190,9 +203,10 @@ describe("NotificationService", () => {
});
});
describe("handleHardwareNotifications", async () => {
describe("handleHardwareNotifications", function() {
let networkResponse;
beforeEach(() => {
beforeEach(function() {
networkResponse = {
monitor: {
name: "Test Monitor",
@@ -225,26 +239,27 @@ describe("NotificationService", () => {
},
};
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("it should return false if no thresholds are set", () => {
it("should return false if no thresholds are set", async () => {
describe("it should return false if no thresholds are set", function() {
it("should return false if no thresholds are set", async function() {
networkResponse.monitor.thresholds = undefined;
const res =
await notificationService.handleHardwareNotifications(networkResponse);
expect(res).to.be.false;
});
it("should return false if metrics are null", async () => {
it("should return false if metrics are null", async function() {
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 () => {
it("should return true if request is well formed and thresholds > 0", async function() {
db.getNotificationsByMonitorId.resolves([
{
type: "email",
@@ -261,7 +276,7 @@ describe("NotificationService", () => {
expect(res).to.be.true;
});
it("should return true if thresholds are exceeded", async () => {
it("should return true if thresholds are exceeded", async function() {
db.getNotificationsByMonitorId.resolves([
{
type: "email",
+21 -12
View File
@@ -4,8 +4,9 @@ import { expect } from "chai";
import NetworkService from "../../service/networkService.js";
const SERVICE_NAME = "SettingsService";
describe("SettingsService", () => {
describe("SettingsService", function() {
let sandbox, mockAppSettings;
beforeEach(function () {
sandbox = sinon.createSandbox();
sandbox.stub(process.env, "CLIENT_HOST").value("http://localhost");
@@ -35,14 +36,16 @@ describe("SettingsService", () => {
sandbox.restore();
sinon.restore();
});
describe("constructor", () => {
it("should construct a new SettingsService", () => {
describe("constructor", function() {
it("should construct a new SettingsService", function() {
const settingsService = new SettingsService(mockAppSettings);
expect(settingsService.appSettings).to.equal(mockAppSettings);
});
});
describe("loadSettings", () => {
it("should load settings from DB when environment variables are not set", async () => {
describe("loadSettings", function() {
it("should load settings from DB when environment variables are not set", async function() {
const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
const appSettings = { findOne: sinon.stub().returns(dbSettings) };
const settingsService = new SettingsService(appSettings);
@@ -50,6 +53,7 @@ describe("SettingsService", () => {
const result = await settingsService.loadSettings();
expect(result).to.deep.equal(dbSettings);
});
it("should throw an error if settings are not found", async function () {
const appSettings = { findOne: sinon.stub().returns(null) };
const settingsService = new SettingsService(appSettings);
@@ -64,7 +68,7 @@ describe("SettingsService", () => {
}
});
it("should add its method and service name to error if not present", async () => {
it("should add its method and service name to error if not present", async function() {
const appSettings = { findOne: sinon.stub().throws(new Error("Test error")) };
const settingsService = new SettingsService(appSettings);
try {
@@ -75,7 +79,8 @@ describe("SettingsService", () => {
expect(error.method).to.equal("loadSettings");
}
});
it("should not add its method and service name to error if present", async () => {
it("should not add its method and service name to error if present", async function() {
const error = new Error("Test error");
error.method = "otherMethod";
error.service = "OTHER_SERVICE";
@@ -89,6 +94,7 @@ describe("SettingsService", () => {
expect(error.method).to.equal("otherMethod");
}
});
it("should merge DB settings with environment variables", async function () {
const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
const appSettings = { findOne: sinon.stub().returns(dbSettings) };
@@ -99,8 +105,9 @@ describe("SettingsService", () => {
expect(settingsService.settings.apiBaseUrl).to.equal("http://localhost");
});
});
describe("reloadSettings", () => {
it("should call loadSettings", async () => {
describe("reloadSettings", function() {
it("should call loadSettings", async function() {
const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
const appSettings = { findOne: sinon.stub().returns(dbSettings) };
const settingsService = new SettingsService(appSettings);
@@ -109,8 +116,9 @@ describe("SettingsService", () => {
expect(result).to.deep.equal(dbSettings);
});
});
describe("getSettings", () => {
it("should return the current settings", () => {
describe("getSettings", function() {
it("should return the current settings", function() {
const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" };
const appSettings = { findOne: sinon.stub().returns(dbSettings) };
const settingsService = new SettingsService(appSettings);
@@ -118,7 +126,8 @@ describe("SettingsService", () => {
const result = settingsService.getSettings();
expect(result).to.deep.equal(dbSettings);
});
it("should throw an error if settings have not been loaded", () => {
it("should throw an error if settings have not been loaded", function() {
const appSettings = { findOne: sinon.stub().returns(null) };
const settingsService = new SettingsService(appSettings);
settingsService.settings = null;
+31 -18
View File
@@ -4,7 +4,8 @@ import { afterEach, describe } from "node:test";
describe("StatusService", () => {
let db, logger, statusService;
beforeEach(() => {
beforeEach(function() {
db = {
getMonitorById: sinon.stub(),
createCheck: sinon.stub(),
@@ -22,31 +23,35 @@ describe("StatusService", () => {
});
describe("constructor", () => {
it("should create an instance of StatusService", () => {
it("should create an instance of StatusService", function() {
expect(statusService).to.be.an.instanceOf(StatusService);
});
});
describe("getStatusString", () => {
it("should return 'up' if status is true", () => {
it("should return 'up' if status is true", function() {
expect(statusService.getStatusString(true)).to.equal("up");
});
it("should return 'down' if status is false", () => {
it("should return 'down' if status is false", function() {
expect(statusService.getStatusString(false)).to.equal("down");
});
it("should return 'unknown' if status is undefined or null", () => {
it("should return 'unknown' if status is undefined or null", function() {
expect(statusService.getStatusString(undefined)).to.equal("unknown");
});
});
describe("updateStatus", async () => {
beforeEach(() => {
describe("updateStatus", () => {
beforeEach(function() {
// statusService.insertCheck = sinon.stub().resolves;
});
afterEach(() => {
sinon.restore();
});
it("should throw an error if an error occurs", async () => {
it("should throw an error if an error occurs", async function() {
const error = new Error("Test error");
statusService.db.getMonitorById = sinon.stub().throws(error);
try {
@@ -56,7 +61,8 @@ describe("StatusService", () => {
}
// expect(statusService.insertCheck.calledOnce).to.be.true;
});
it("should return {statusChanged: false} if status hasn't changed", async () => {
it("should return {statusChanged: false} if status hasn't changed", async function() {
statusService.db.getMonitorById = sinon.stub().returns({ status: true });
const result = await statusService.updateStatus({
monitorId: "test",
@@ -65,7 +71,8 @@ describe("StatusService", () => {
expect(result).to.deep.equal({ statusChanged: false });
// expect(statusService.insertCheck.calledOnce).to.be.true;
});
it("should return {statusChanged: true} if status has changed from down to up", async () => {
it("should return {statusChanged: true} if status has changed from down to up", async function() {
statusService.db.getMonitorById = sinon
.stub()
.returns({ status: false, save: sinon.stub() });
@@ -78,7 +85,8 @@ describe("StatusService", () => {
expect(result.prevStatus).to.be.false;
// expect(statusService.insertCheck.calledOnce).to.be.true;
});
it("should return {statusChanged: true} if status has changed from up to down", async () => {
it("should return {statusChanged: true} if status has changed from up to down", async function() {
statusService.db.getMonitorById = sinon
.stub()
.returns({ status: true, save: sinon.stub() });
@@ -94,7 +102,7 @@ describe("StatusService", () => {
});
describe("buildCheck", () => {
it("should build a check object", () => {
it("should build a check object", function() {
const check = statusService.buildCheck({
monitorId: "test",
type: "test",
@@ -111,7 +119,7 @@ describe("StatusService", () => {
expect(check.message).to.equal("Test message");
});
it("should build a check object for pagespeed type", () => {
it("should build a check object for pagespeed type", function() {
const check = statusService.buildCheck({
monitorId: "test",
type: "pagespeed",
@@ -154,7 +162,8 @@ describe("StatusService", () => {
tbt: { score: 1 },
});
});
it("should build a check object for pagespeed type with missing data", () => {
it("should build a check object for pagespeed type with missing data", function() {
const check = statusService.buildCheck({
monitorId: "test",
type: "pagespeed",
@@ -186,7 +195,8 @@ describe("StatusService", () => {
tbt: 0,
});
});
it("should build a check for hardware type", () => {
it("should build a check for hardware type", function() {
const check = statusService.buildCheck({
monitorId: "test",
type: "hardware",
@@ -206,7 +216,8 @@ describe("StatusService", () => {
expect(check.disk).to.equal("disk");
expect(check.host).to.equal("host");
});
it("should build a check for hardware type with missing data", () => {
it("should build a check for hardware type with missing data", function() {
const check = statusService.buildCheck({
monitorId: "test",
type: "hardware",
@@ -227,8 +238,9 @@ describe("StatusService", () => {
expect(check.host).to.deep.equal({});
});
});
describe("insertCheck", () => {
it("should log an error if one is thrown", async () => {
it("should log an error if one is thrown", async function() {
const testError = new Error("Test error");
statusService.db.createCheck = sinon.stub().throws(testError);
try {
@@ -238,7 +250,8 @@ describe("StatusService", () => {
}
expect(statusService.logger.error.calledOnce).to.be.true;
});
it("should insert a check into the database", async () => {
it("should insert a check into the database", async function() {
await statusService.insertCheck({ monitorId: "test", type: "http" });
expect(statusService.db.createCheck.calledOnce).to.be.true;
});
+6 -6
View File
@@ -1,8 +1,8 @@
import { NormalizeData, calculatePercentile } from "../../utils/dataUtils.js";
import sinon from "sinon";
describe("NormalizeData", () => {
it("should normalize response times when checks length is greater than 1", () => {
describe("NormalizeData", function() {
it("should normalize response times when checks length is greater than 1", function() {
const checks = [
{ responseTime: 20, _doc: { id: 1 } },
{ responseTime: 40, _doc: { id: 2 } },
@@ -21,7 +21,7 @@ describe("NormalizeData", () => {
});
});
it("should return checks with original response times when checks length is 1", () => {
it("should return checks with original response times when checks length is 1", function() {
const checks = [{ responseTime: 20, _doc: { id: 1 } }];
const rangeMin = 1;
const rangeMax = 100;
@@ -32,7 +32,7 @@ describe("NormalizeData", () => {
expect(result[0]).to.have.property("originalResponseTime", 20);
});
it("should handle edge cases with extreme response times", () => {
it("should handle edge cases with extreme response times", function() {
const checks = [
{ responseTime: 5, _doc: { id: 1 } },
{ responseTime: 95, _doc: { id: 2 } },
@@ -49,8 +49,8 @@ describe("NormalizeData", () => {
});
});
describe("calculatePercentile", () => {
it("should return the lower value when upper is greater than or equal to the length of the sorted array", () => {
describe("calculatePercentile", function() {
it("should return the lower value when upper is greater than or equal to the length of the sorted array", function() {
const checks = [
{ responseTime: 10 },
{ responseTime: 20 },
+3 -3
View File
@@ -3,8 +3,8 @@ import sinon from "sinon";
import sharp from "sharp";
import { GenerateAvatarImage } from "../../utils/imageProcessing.js";
describe("imageProcessing - GenerateAvatarImage", () => {
it("should resize the image to 64x64 and return a base64 string", async () => {
describe("imageProcessing - GenerateAvatarImage", function() {
it("should resize the image to 64x64 and return a base64 string", async function() {
const file = {
buffer: Buffer.from("test image buffer"),
};
@@ -30,7 +30,7 @@ describe("imageProcessing - GenerateAvatarImage", () => {
sharpStub.restore();
});
it("should throw an error if resizing fails", async () => {
it("should throw an error if resizing fails", async function() {
const file = {
buffer: Buffer.from("test image buffer"),
};
+11 -10
View File
@@ -3,20 +3,20 @@ import logger from "../../utils/logger.js";
import { Logger } from "../../utils/logger.js";
import winston from "winston";
describe("Logger", () => {
describe("Logger", function() {
let infoStub, warnStub, errorStub;
beforeEach(() => {
beforeEach(function() {
infoStub = sinon.stub(logger.logger, "info");
warnStub = sinon.stub(logger.logger, "warn");
errorStub = sinon.stub(logger.logger, "error");
});
afterEach(() => {
afterEach(function() {
sinon.restore();
});
describe("constructor", () => {
describe("constructor", function() {
let createLoggerStub;
beforeEach(function () {
@@ -46,6 +46,7 @@ describe("Logger", () => {
const logger = new Logger();
logger.logger.info(logMessage);
});
it("should convert details to JSON string if it is an object", function () {
const logDetails = { key: "value" };
const expectedDetails = JSON.stringify(logDetails, null, 2); // Removed .s
@@ -68,8 +69,8 @@ describe("Logger", () => {
});
});
describe("info", () => {
it("should log an informational message", () => {
describe("info", function() {
it("should log an informational message", function() {
const config = {
message: "Info message",
service: "TestService",
@@ -90,8 +91,8 @@ describe("Logger", () => {
});
});
describe("warn", () => {
it("should log a warning message", () => {
describe("warn", function() {
it("should log a warning message", function() {
const config = {
message: "Warning message",
service: "TestService",
@@ -112,8 +113,8 @@ describe("Logger", () => {
});
});
describe("error", () => {
it("should log an error message", () => {
describe("error", function() {
it("should log an error message", function() {
const config = {
message: "Error message",
service: "TestService",
+6 -6
View File
@@ -1,14 +1,14 @@
import { errorMessages, successMessages } from "../../utils/messages.js";
describe("Messages", () => {
describe("messages - errorMessages", () => {
it("should have a DB_FIND_MONITOR_BY_ID function", () => {
describe("Messages", function() {
describe("messages - errorMessages", function() {
it("should have a DB_FIND_MONITOR_BY_ID function", function() {
const monitorId = "12345";
expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal(
`Monitor with id ${monitorId} not found`
);
});
it("should have a DB_DELETE_CHECKS function", () => {
it("should have a DB_DELETE_CHECKS function", function() {
const monitorId = "12345";
expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal(
`No checks found for monitor with id ${monitorId}`
@@ -16,8 +16,8 @@ describe("Messages", () => {
});
});
describe("messages - successMessages", () => {
it("should have a MONITOR_GET_BY_USER_ID function", () => {
describe("messages - successMessages", function() {
it("should have a MONITOR_GET_BY_USER_ID function", function() {
const userId = "12345";
expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal(
`Got monitor for ${userId} successfully"`
+11 -11
View File
@@ -1,49 +1,49 @@
import { ParseBoolean, getTokenFromHeaders } from "../../utils/utils.js";
describe("utils - ParseBoolean", () => {
it("should return true", () => {
describe("utils - ParseBoolean", function() {
it("should return true", function() {
const result = ParseBoolean("true");
expect(result).to.be.true;
});
it("should return false", () => {
it("should return false", function() {
const result = ParseBoolean("false");
expect(result).to.be.false;
});
it("should return false", () => {
it("should return false", function() {
const result = ParseBoolean(null);
expect(result).to.be.false;
});
it("should return false", () => {
it("should return false", function() {
const result = ParseBoolean(undefined);
expect(result).to.be.false;
});
});
describe("utils - getTokenFromHeaders", () => {
it("should throw an error if authorization header is missing", () => {
describe("utils - getTokenFromHeaders", function() {
it("should throw an error if authorization header is missing", function() {
const headers = {};
expect(() => getTokenFromHeaders(headers)).to.throw("No auth headers");
});
it("should throw an error if authorization header does not start with Bearer", () => {
it("should throw an error if authorization header does not start with Bearer", function() {
const headers = { authorization: "Basic abcdef" };
expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
});
it("should return the token if authorization header is correctly formatted", () => {
it("should return the token if authorization header is correctly formatted", function() {
const headers = { authorization: "Bearer abcdef" };
expect(getTokenFromHeaders(headers)).to.equal("abcdef");
});
it("should throw an error if authorization header has more than two parts", () => {
it("should throw an error if authorization header has more than two parts", function() {
const headers = { authorization: "Bearer abc def" };
expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
});
it("should throw an error if authorization header has less than two parts", () => {
it("should throw an error if authorization header has less than two parts", function() {
const headers = { authorization: "Bearer" };
expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
});
+50 -2
View File
@@ -8,12 +8,23 @@ const calculatePercentile = (arr, percentile) => {
return sorted[lower].responseTime * (1 - weight) + sorted[upper].responseTime * weight;
};
const calculatePercentileUptimeDetails = (arr, percentile) => {
const sorted = arr.slice().sort((a, b) => a.avgResponseTime - b.avgResponseTime);
const index = (percentile / 100) * (sorted.length - 1);
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
if (upper >= sorted.length) return sorted[lower].avgResponseTime;
return (
sorted[lower].avgResponseTime * (1 - weight) + sorted[upper].avgResponseTime * weight
);
};
const NormalizeData = (checks, rangeMin, rangeMax) => {
if (checks.length > 1) {
// Get the 5th and 95th percentile
const min = calculatePercentile(checks, 0);
const max = calculatePercentile(checks, 95);
const normalizedChecks = checks.map((check) => {
const originalResponseTime = check.responseTime;
// Normalize the response time between 1 and 100
@@ -40,5 +51,42 @@ const NormalizeData = (checks, rangeMin, rangeMax) => {
});
}
};
const NormalizeDataUptimeDetails = (checks, rangeMin, rangeMax) => {
if (checks.length > 1) {
// Get the 5th and 95th percentile
const min = calculatePercentileUptimeDetails(checks, 0);
const max = calculatePercentileUptimeDetails(checks, 95);
export { calculatePercentile, NormalizeData };
const normalizedChecks = checks.map((check) => {
const originalResponseTime = check.avgResponseTime;
// Normalize the response time between 1 and 100
let normalizedResponseTime =
rangeMin + ((check.avgResponseTime - min) * (rangeMax - rangeMin)) / (max - min);
// Put a floor on the response times so we don't have extreme outliers
// Better visuals
normalizedResponseTime = Math.max(
rangeMin,
Math.min(rangeMax, normalizedResponseTime)
);
return {
...check,
avgResponseTime: normalizedResponseTime,
originalAvgResponseTime: originalResponseTime,
};
});
return normalizedChecks;
} else {
return checks.map((check) => {
return { ...check, originalResponseTime: check.responseTime };
});
}
};
export {
calculatePercentile,
NormalizeData,
calculatePercentileUptimeDetails,
NormalizeDataUptimeDetails,
};
+10
View File
@@ -232,6 +232,14 @@ const getMonitorURLByQueryValidation = joi.object({
monitorURL: joi.string().uri().required(),
});
const getHardwareDetailsByIdParamValidation = joi.object({
monitorId: joi.string().required(),
});
const getHardwareDetailsByIdQueryValidation = joi.object({
dateRange: joi.string().valid("day", "week", "month"),
});
//****************************************
// Alerts
//****************************************
@@ -465,6 +473,8 @@ export {
getMonitorsByTeamIdQueryValidation,
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,
getHardwareDetailsByIdParamValidation,
getHardwareDetailsByIdQueryValidation,
getCertificateParamValidation,
editMonitorBodyValidation,
pauseMonitorParamValidation,
+83
View File
@@ -0,0 +1,83 @@
<h1>Announcing USD 5K Grant Prize for Distributed Uptime Management Development</h1>
### January 9, 2025, [Checkmate](https://github.com/bluewave-labs/checkmate) & [UpRock](https://uprock.com/)
**Checkmate** and **UpRock** are excited to announce a USD 5,000 grant prize funding opportunity to support the development of the next-generation distributed uptime manager. UpRock, a DePIN platform with over 2 million devices worldwide, provides the foundation for Checkmate to monitor server uptime from across the globe effectively.
This grant funding aims to empower developers to contribute innovative solutions to enhance Checkmate's functionality and usability for distributed uptime management, powered by **UpRock**.
## Program details
* **Submission start date:** January 9, 2025
* **Submission deadline:** January 20, 2025
* **Announcement of prewinners:** January 25, 2025
* **Total prize pool:** USD 5,000 (subject to change depending on the quality or number submissions)
* **Completion timeline:** A maximum of 30 days after pre-selection
## What we’re looking for
* Participants are encouraged to submit ideas and features that:
* Enhance Checkmate & UpRock's distributed uptime monitoring capabilities.
* Simplify deployment for businesses and scale effectively.
* Deliver innovative value-adds to Checkmate and UpRock platform.
* Improve UI/UX consistency, performance, and monitoring reliability.
Here are some of the sample ideas you can select from, or come up with your own.
* **Project 1 (DePIN sourced intelligence):** Build a global internet speed survey using 3M devices. This dashboard will implement the underlying Checkmate & UpRock infrastructure and build maps and tables for: 1. Upload speed, 2. Download speed, 3. Ping numbers.
* **Project 2:** Build a notification system that will cover Discord, Slack, Telegram and webhooks.
* **Project 3:** Implement a i18n library for Checkmate. The translation framework will include modifying the source code and using an open source translation manager online so other translators can also contribute. This will help reach out to more developers to build DePIN features on top of Checkmate.
* **Project 4:** A plugin system and implementation for Checkmate to help add more UpRock platform functionality. This plugin system will also be used to add more plugins/features on top of Checkmate easily.
* **Project 5 (Checkmate CLI):** When installed via brew or curl, this CLI will help admins manage the monitoring setup directly from the command line. Admin will be able to list, check, trigger the monitors from the command line easily or change password of the users, or create/invite a new user.
* **Project 6:** Ability to tag or group monitors. Each monitor will have its own tagging, and the admin will be able to filter monitors in the table.
* **Project 7:** Deploy Checkmate and write a blog post about it on dev.to or Hackernoon. Bonus points if you market it on social media.
## How to participate
1. **Submit your ideas:** Fill out our online form to propose enhancements and features for Checkmate powered by UpRock, until January 20, 2025. You can submit a project based on the ideas above, or you can also come up with your own enhancement projects.
2. S**election criteria:** Prewinners will be selected based on functionality, design and impact.
3. **Implementation phase:** Prewinners will have 21 days to develop and submit their code contributions starting from January 25, 2025.
4. **Quality requirements:** Submitted code must:
- Be high quality and free from bugs.
- Pass extensive QA testing
- Adhere to Checkmate’s UI design and development standards.
5. **Grant disbursement:** Upon code acceptance via pull, winners will receive USD 1,000 each, sent to their bank account or Paypal.
## Timeline
- **January 10, 2025:** Grant announcement and form open for submissions.
- **January 20, 2025:** Submission deadline
- **January 25, 2025:** Winners announced.
- **21 days post-selection:** Code development and submission period.
- **Grant payment:** Issued in 14 days upon PR acceptance.
## Rules
- **Cap enforcement:** All projects submitted to the Checkmate & UpRock grant program will be subject to a strict funding cap of $1000 per project. This rule ensures that funding is distributed fairly among contributors, encouraging a diverse range of submissions. Applicants are encouraged to scope their projects accordingly to stay within this limit. Proposals exceeding this amount may either be revised or considered for partial funding.
- **Multiple submissions:** Contributors are welcome to submit multiple projects, provided each submission adheres to the specified funding cap and size criteria. This approach allows developers to showcase various ideas, features, and improvements without being constrained to a single proposal. Each project will be evaluated independently, ensuring fair consideration regardless of other submissions by the same contributor.
- **Partial funding:** In cases where a proposal exceeds the budget limit or includes additional features that extend beyond the core requirements, partial funding may be offered. Contributors will receive feedback and recommendations to adjust their project scope to align with the funding guidelines. This flexibility ensures that high-value features and enhancements can still be supported, even if full funding is not feasible.
- **Feedback process:** To support continuous improvement, contributors will receive constructive feedback on their submissions, whether accepted or not. Feedback will focus on areas such as code quality, design patterns, documentation and usability. Developers can use this input to refine their proposals, submit follow-ups, or enhance their future contributions.
- **Final review and recognition:** A portion of the total funding, $500 (10%), will be reserved for recognizing outstanding contributions across all categories. This award, labeled the "Best Overall Contribution" prize, will be given to a project that demonstrates exceptional innovation, technical quality, and impact. The selection process will be based on the same evaluation criteria as other submissions, with additional emphasis on originality and long-term value to the Checkmate & UpRock ecosystem.
## Funding tiers
- **4-5 small size projects**
- Examples: Usability fixes, UI enhancements, updating test cases, writing command line interfaces or documentation updates.
- Reward: $100–$300
- **4-5 medium size projects**
- Examples: New monitoring modules/additions, API enhancements, performance improvements or feature additions.
- Reward: $300–$600
- **1-2 large projects**
- Examples: Integrations with 3rd party / external tools, major scalability enhancements, advanced analytics features, complex monitoring capabilities, better graphics/charts.
- Reward: $600–$1000
## Evaluation criteria
- **Innovation (30%):** Uniqueness and creativity of the solution.
- **Contribution value (30%):** Impact on usability, performance, or scalability.
- **Code quality & design (40%):** Clean, documented and tested code with working examples showing screenshots and/or videos.
## Ready to innovate for distributed uptime?
Don’t miss this opportunity to shape the future of distributed uptime monitoring with Checkmate and UpRock! Submit your ideas today and join us in building cutting-edge solutions for global server management.
Visit Link to submit your entry.
+16 -1
View File
@@ -117,7 +117,22 @@ VITE_APP_LOG_LEVEL="debug"
Configure the server with the following environmental variables:
<table><thead><tr><th width="239">ENV Variable Name</th><th width="149">Required/Optional</th><th width="116">Type</th><th>Description</th><th>Accepted Values</th></tr></thead><tbody><tr><td>CLIENT_HOST</td><td>Required</td><td><code>string</code></td><td>Frontend Host</td><td></td></tr><tr><td>JWT_SECRET</td><td>Required</td><td><code>string</code></td><td>JWT secret</td><td></td></tr><tr><td>REFRESH_TOKEN_SECRET</td><td>Required</td><td><code>string</code></td><td>Refresh JWT secret</td><td></td></tr><tr><td>DB_TYPE</td><td>Optional</td><td><code>string</code></td><td>Specify DB to use</td><td><code>MongoDB | FakeDB</code></td></tr><tr><td>DB_CONNECTION_STRING</td><td>Required</td><td><code>string</code></td><td>Specifies URL for MongoDB Database</td><td></td></tr><tr><td>PORT</td><td>Optional</td><td><code>integer</code></td><td>Specifies Port for Server</td><td></td></tr><tr><td>LOGIN_PAGE_URL</td><td>Required</td><td><code>string</code></td><td>Login url to be used in emailing service</td><td></td></tr><tr><td>REDIS_HOST</td><td>Required</td><td><code>string</code></td><td>Host address for Redis database</td><td></td></tr><tr><td>REDIS_PORT</td><td>Required</td><td><code>integer</code></td><td>Port for Redis database</td><td></td></tr><tr><td>TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for token to live</td><td>In vercel/ms format https://github.com/vercel/ms</td></tr><tr><td>REFRESH_TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for refresh token to live</td><td></td></tr><tr><td>PAGESPEED_API_KEY</td><td>Optional</td><td><code>string</code></td><td>API Key for PageSpeed requests</td><td></td></tr><tr><td>SYSTEM_EMAIL_HOST</td><td>Required</td><td><code>string</code></td><td>Host to send System Emails From</td><td></td></tr><tr><td>SYSTEM_EMAIL_PORT</td><td>Required</td><td><code>number</code></td><td>Port for System Email Host</td><td></td></tr><tr><td>SYSTEM_EMAIL_ADDRESS</td><td>Required</td><td><code>string</code></td><td>System Email Address</td><td></td></tr><tr><td>SYSTEM_EMAIL_PASSWORD</td><td>Required</td><td><code>string</code></td><td>System Email Password</td><td></td></tr></tbody></table>
<table><thead><tr><th width="239">ENV Variable Name</th><th width="149">Required/Optional</th><th width="116">Type</th><th>Description</th><th>Accepted Values</th></tr></thead><tbody><tr><td>CLIENT_HOST</td><td>Required</td><td><code>string</code></td><td>Frontend Host</td><td></td></tr><tr><td>JWT_SECRET</td><td>Required</td><td><code>string</code></td><td>JWT secret</td><td></td></tr><tr><td>REFRESH_TOKEN_SECRET</td><td>Required</td><td><code>string</code></td><td>Refresh JWT secret</td><td></td></tr><tr><td>DB_TYPE</td><td>Optional</td><td><code>string</code></td><td>Specify DB to use</td><td><code>MongoDB | FakeDB</code></td></tr><tr><td>DB_CONNECTION_STRING</td><td>Required</td><td><code>string</code></td><td>Specifies URL for MongoDB Database</td><td></td></tr><tr><td>PORT</td><td>Optional</td><td><code>integer</code></td><td>Specifies Port for Server</td><td></td></tr><tr><td>LOGIN_PAGE_URL</td><td>Required</td><td><code>string</code></td><td>Login url to be used in emailing service</td><td></td></tr><tr><td>REDIS_HOST</td><td>Required</td><td><code>string</code></td><td>Host address for Redis database</td><td></td></tr><tr><td>REDIS_PORT</td><td>Required</td><td><code>integer</code></td><td>Port for Redis database</td><td></td></tr><tr><td>TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for token to live</td><td>In vercel/ms format https://github.com/vercel/ms</td></tr><tr><td>REFRESH_TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for refresh token to live</td><td></td></tr><tr><td>PAGESPEED_API_KEY</td><td>Optional</td><td><code>string</code></td><td>API Key for PageSpeed requests</td><td></td></tr><tr><td>SYSTEM_EMAIL_HOST</td><td>Required</td><td><code>string</code></td><td>Host to send System Emails From</td><td></td></tr><tr><td>SYSTEM_EMAIL_PORT</td><td>Required</td><td><code>number</code></td><td>Port for System Email Host</td><td></td></tr><tr><td>SYSTEM_EMAIL_ADDRESS</td><td>Required</td><td><code>string</code></td><td>System Email Address</td><td></td></tr><tr><td>SYSTEM_EMAIL_PASSWORD</td><td>Required</td><td><code>string</code></td><td>System Email Password</td><td></td></tr>
<tr>
<td>NODE_ENV</td>
<td>Optional</td>
<td><code>string</code></td>
<td>Application mode</td>
<td><code>development | production</code></td>
</tr>
<tr>
<td>NGROK_AUTH_TOKEN</td>
<td>Optional</td>
<td><code>string</code></td>
<td>Auth token for ngrok</td>
<td><code>"auth_token"</code></td>
</tr>
</tbody></table>
Sample env file