diff --git a/Client/src/Components/Inputs/Field/index.jsx b/Client/src/Components/Inputs/Field/index.jsx
index 1843a411b..9dbf616a9 100644
--- a/Client/src/Components/Inputs/Field/index.jsx
+++ b/Client/src/Components/Inputs/Field/index.jsx
@@ -74,6 +74,7 @@ const Field = forwardRef(
"&:has(.input-error) .MuiOutlinedInput-root fieldset": {
borderColor: theme.palette.error.text,
},
+ display: hidden ? "none" : "",
}}
>
{label && (
@@ -142,7 +143,7 @@ const Field = forwardRef(
},
}
: {
- display: hidden ? "none" : "",
+
}
}
InputProps={{
diff --git a/Client/src/Components/Sidebar/index.jsx b/Client/src/Components/Sidebar/index.jsx
index 200c5dd01..0019a282a 100644
--- a/Client/src/Components/Sidebar/index.jsx
+++ b/Client/src/Components/Sidebar/index.jsx
@@ -41,6 +41,9 @@ import ArrowUp from "../../assets/icons/up-arrow.svg?react";
import ArrowRight from "../../assets/icons/right-arrow.svg?react";
import ArrowLeft from "../../assets/icons/left-arrow.svg?react";
import DotsVertical from "../../assets/icons/dots-vertical.svg?react";
+import ChangeLog from "../../assets/icons/changeLog.svg?react";
+import Docs from "../../assets/icons/docs.svg?react";
+import Folder from "../../assets/icons/folder.svg?react";
import "./index.css";
@@ -66,12 +69,30 @@ const menu = [
{ name: "Team", path: "account/team", icon: },
],
},
+ {
+ name: "Other",
+ icon: ,
+ nested: [
+ { name: "Settings", path: "settings", icon: },
+ { name: "Support", path: "support", icon: },
+ { name: "Docs", path: "docs", icon: },
+ { name: "Changelog", path: "changelog", icon: },
+ ],
+ },
];
-const other = [
- { name: "Support", path: "support", icon: },
- { name: "Settings", path: "settings", icon: },
-];
+const URL_MAP = {
+ support: "https://github.com/bluewave-labs/bluewave-uptime/issues",
+ docs: "https://bluewavelabs.gitbook.io/uptime-manager",
+ changelog: "https://github.com/bluewave-labs/bluewave-uptime/releases",
+};
+
+const PATH_MAP = {
+ monitors: "Dashboard",
+ pagespeed: "Dashboard",
+ account: "Account",
+ settings: "Other",
+};
/**
* @component
@@ -87,7 +108,7 @@ function Sidebar() {
const dispatch = useDispatch();
const authState = useSelector((state) => state.auth);
const collapsed = useSelector((state) => state.ui.sidebar.collapsed);
- const [open, setOpen] = useState({ Dashboard: false, Account: false });
+ const [open, setOpen] = useState({ Dashboard: false, Account: false, Other: false });
const [anchorEl, setAnchorEl] = useState(null);
const [popup, setPopup] = useState();
const { user } = useSelector((state) => state.auth);
@@ -120,13 +141,13 @@ function Sidebar() {
};
useEffect(() => {
- if (
- location.pathname.includes("monitors") ||
- location.pathname.includes("pagespeed")
- )
- setOpen((prev) => ({ ...prev, Dashboard: true }));
- else if (location.pathname.includes("/account"))
- setOpen((prev) => ({ ...prev, Account: true }));
+ const matchedKey = Object.keys(PATH_MAP).find((key) =>
+ location.pathname.includes(key)
+ );
+
+ if (matchedKey) {
+ setOpen((prev) => ({ ...prev, [PATH_MAP[matchedKey]]: true }));
+ }
}, []);
return (
@@ -202,7 +223,9 @@ function Sidebar() {
},
}}
onClick={() => {
- setOpen({ Dashboard: false, Account: false });
+ setOpen(prev =>
+ Object.fromEntries(Object.keys(prev).map(key => [key, false]))
+ )
dispatch(toggleSidebar());
}}
>
@@ -346,7 +369,12 @@ function Sidebar() {
}
key={child.path}
onClick={() => {
- navigate(`/${child.path}`);
+ const url = URL_MAP[child.path];
+ if (url) {
+ window.open(url, "_blank", "noreferrer");
+ } else {
+ navigate(`/${child.path}`);
+ }
closePopup();
}}
sx={{
@@ -372,8 +400,8 @@ function Sidebar() {
setOpen((prev) => ({
- ...prev,
- [`${item.name}`]: !prev[`${item.name}`],
+ ...Object.fromEntries(Object.keys(prev).map(key => [key, false])),
+ [item.name]: !prev[item.name]
}))
}
sx={{
@@ -409,7 +437,14 @@ function Sidebar() {
: ""
}
key={child.path}
- onClick={() => navigate(`/${child.path}`)}
+ onClick={() => {
+ const url = URL_MAP[child.path];
+ if (url) {
+ window.open(url, "_blank", "noreferrer");
+ } else {
+ navigate(`/${child.path}`);
+ }
+ }}
sx={{
gap: theme.spacing(4),
borderRadius: theme.shape.borderRadius,
@@ -455,70 +490,6 @@ function Sidebar() {
)
)}
-
- {/* other */}
-
)}
{collapsed && }
-
+ */}
)}
+ {isAdmin && (
+
+ )}
{isAdmin && (
- )}
+ )}
{
const { isLoading } = useSelector((state) => state.uptimeMonitors);
const { isLoading: authIsLoading } = useSelector((state) => state.auth);
const { timezone } = useSelector((state) => state.ui);
+ const {mode} = useSelector((state) => state.ui);
const [checksIsLoading, setChecksIsLoading] = useState(false);
const [form, setForm] = useState({
ttl: checkTTL ? (checkTTL / SECONDS_PER_DAY).toString() : 0,
@@ -188,6 +189,26 @@ const Settings = ({ isAdmin }) => {
/>
+
+
+ Appearance
+
+ Switch between light and dark mode.
+
+
+
+
+
+
{isAdmin && (
diff --git a/Client/src/assets/icons/changeLog.svg b/Client/src/assets/icons/changeLog.svg
new file mode 100644
index 000000000..d12f6c10e
--- /dev/null
+++ b/Client/src/assets/icons/changeLog.svg
@@ -0,0 +1,3 @@
+
diff --git a/Client/src/assets/icons/docs.svg b/Client/src/assets/icons/docs.svg
new file mode 100644
index 000000000..d36458de2
--- /dev/null
+++ b/Client/src/assets/icons/docs.svg
@@ -0,0 +1,3 @@
+
diff --git a/Client/src/assets/icons/folder.svg b/Client/src/assets/icons/folder.svg
new file mode 100644
index 000000000..15941e5b5
--- /dev/null
+++ b/Client/src/assets/icons/folder.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Server/.nycrc b/Server/.nycrc
index da9bccbfc..d6406bf7e 100644
--- a/Server/.nycrc
+++ b/Server/.nycrc
@@ -1,6 +1,6 @@
{
"all": true,
- "include": ["**/*.js"],
+ "include": ["controllers/*.js"],
"exclude": ["**/*.test.js"],
"reporter": ["html", "text", "lcov"],
"sourceMap": false,
diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js
index 36fdc73cb..136a05a7a 100644
--- a/Server/controllers/authController.js
+++ b/Server/controllers/authController.js
@@ -68,6 +68,7 @@ const registerUser = async (req, res, next) => {
}
const newUser = await req.db.insertUser({ ...req.body }, req.file);
+
logger.info(successMessages.AUTH_CREATE_USER, {
service: SERVICE_NAME,
userId: newUser._id,
@@ -78,7 +79,9 @@ const registerUser = async (req, res, next) => {
delete userForToken.avatarImage;
const appSettings = await req.settingsService.getSettings();
+
const token = issueToken(userForToken, appSettings);
+
req.emailService
.buildAndSendEmail(
"welcomeEmailTemplate",
@@ -99,6 +102,7 @@ const registerUser = async (req, res, next) => {
data: { user: newUser, token: token },
});
} catch (error) {
+ console.log("ERROR", error);
next(handleError(error, SERVICE_NAME, "registerController"));
}
};
@@ -293,6 +297,8 @@ const requestRecovery = async (req, res, next) => {
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
data: msgId,
});
+ } else {
+ throw new Error(errorMessages.FRIENDLY_ERROR);
}
} catch (error) {
next(handleError(error, SERVICE_NAME, "recoveryRequestController"));
@@ -435,6 +441,7 @@ const getAllUsers = async (req, res) => {
};
module.exports = {
+ issueToken,
registerUser,
loginUser,
editUser,
diff --git a/Server/controllers/controllerUtils.js b/Server/controllers/controllerUtils.js
index 1b3994f2f..3238cd2a1 100644
--- a/Server/controllers/controllerUtils.js
+++ b/Server/controllers/controllerUtils.js
@@ -6,8 +6,8 @@ const handleValidationError = (error, serviceName) => {
return error;
};
-const handleError = (error, serviceName, method, code = 500) => {
- error.code === undefined ? (error.code = code) : null;
+const handleError = (error, serviceName, method, status = 500) => {
+ error.status === undefined ? (error.status = status) : null;
error.service === undefined ? (error.service = serviceName) : null;
error.method === undefined ? (error.method = method) : null;
return error;
diff --git a/Server/controllers/inviteController.js b/Server/controllers/inviteController.js
index 56d6b08f6..b22f9b2cc 100644
--- a/Server/controllers/inviteController.js
+++ b/Server/controllers/inviteController.js
@@ -8,17 +8,7 @@ require("dotenv").config();
const jwt = require("jsonwebtoken");
const { handleError, handleValidationError } = require("./controllerUtils");
const SERVICE_NAME = "inviteController";
-
-const getTokenFromHeaders = (headers) => {
- const authorizationHeader = headers.authorization;
- if (!authorizationHeader) throw new Error("No auth headers");
-
- const parts = authorizationHeader.split(" ");
- if (parts.length !== 2 || parts[0] !== "Bearer")
- throw new Error("Invalid auth headers");
-
- return parts[1];
-};
+const { getTokenFromHeaders } = require("../utils/utils");
/**
* Issues an invitation to a new user. Only admins can invite new users. An invitation token is created and sent via email.
@@ -93,6 +83,6 @@ const inviteVerifyController = async (req, res, next) => {
};
module.exports = {
- inviteController: issueInvitation,
+ issueInvitation,
inviteVerifyController,
};
diff --git a/Server/controllers/maintenanceWindowController.js b/Server/controllers/maintenanceWindowController.js
index c6c34b7eb..933da41ea 100644
--- a/Server/controllers/maintenanceWindowController.js
+++ b/Server/controllers/maintenanceWindowController.js
@@ -84,7 +84,7 @@ const getMaintenanceWindowsByTeamId = async (req, res, next) => {
req.query
);
- return res.status(201).json({
+ return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM,
data: maintenanceWindows,
@@ -109,7 +109,7 @@ const getMaintenanceWindowsByMonitorId = async (req, res, next) => {
req.params.monitorId
);
- return res.status(201).json({
+ return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER,
data: maintenanceWindows,
@@ -128,7 +128,7 @@ const deleteMaintenanceWindow = async (req, res, next) => {
}
try {
await req.db.deleteMaintenanceWindowById(req.params.id);
- return res.status(201).json({
+ return res.status(200).json({
success: true,
msg: successMessages.MAINTENANCE_WINDOW_DELETE,
});
diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js
index b7e27451b..9ef90fa4b 100644
--- a/Server/controllers/monitorController.js
+++ b/Server/controllers/monitorController.js
@@ -33,7 +33,7 @@ const { handleError, handleValidationError } = require("./controllerUtils");
const getAllMonitors = async (req, res, next) => {
try {
const monitors = await req.db.getAllMonitors();
- return res.json({
+ return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_ALL,
data: monitors,
@@ -63,7 +63,7 @@ const getMonitorStatsById = async (req, res, next) => {
try {
const monitorStats = await req.db.getMonitorStatsById(req);
- return res.json({
+ return res.status(200).json({
success: true,
msg: successMessages.MONITOR_STATS_BY_ID,
data: monitorStats,
@@ -86,7 +86,7 @@ const getMonitorCertificate = async (req, res, next) => {
const monitorUrl = new URL(monitor.url);
const certificate = await sslChecker(monitorUrl.hostname);
if (certificate && certificate.validTo) {
- return res.json({
+ return res.status(200).json({
success: true,
msg: successMessages.MONITOR_CERTIFICATE,
data: {
@@ -94,7 +94,7 @@ const getMonitorCertificate = async (req, res, next) => {
},
});
} else {
- return res.json({
+ return res.status(200).json({
success: true,
msg: successMessages.MONITOR_CERTIFICATE,
data: { certificateDate: "N/A" },
@@ -130,9 +130,10 @@ const getMonitorById = async (req, res, next) => {
if (!monitor) {
const error = new Error(errorMessages.MONITOR_GET_BY_ID);
error.status = 404;
+ throw error;
}
- return res.json({
+ return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_ID,
data: monitor,
@@ -207,7 +208,7 @@ const getMonitorsByTeamId = async (req, res, next) => {
try {
const teamId = req.params.teamId;
const monitors = await req.db.getMonitorsByTeamId(req, res);
- return res.json({
+ return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
data: monitors,
@@ -324,14 +325,23 @@ const deleteAllMonitors = async (req, res, next) => {
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId);
- await monitors.forEach(async (monitor) => {
- await req.jobQueue.deleteJob(monitor);
- await req.db.deleteChecks(monitor._id);
- await req.db.deleteAlertByMonitorId(monitor._id);
- await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
- await req.db.deleteNotificationsByMonitorId(monitor._id);
- });
-
+ await Promise.all(
+ monitors.map(async (monitor) => {
+ try {
+ await req.jobQueue.deleteJob(monitor);
+ await req.db.deleteChecks(monitor._id);
+ await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
+ await req.db.deleteNotificationsByMonitorId(monitor._id);
+ } catch (error) {
+ logger.error(
+ `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
+ {
+ method: "deleteAllMonitors",
+ }
+ );
+ }
+ })
+ );
return res
.status(200)
.json({ success: true, msg: `Deleted ${deletedCount} monitors` });
@@ -459,7 +469,7 @@ const addDemoMonitors = async (req, res, next) => {
return res.status(200).json({
success: true,
- message: successMessages.MONITOR_DEMO_ADDED,
+ msg: successMessages.MONITOR_DEMO_ADDED,
data: demoMonitors.length,
});
} catch (error) {
diff --git a/Server/controllers/queueController.js b/Server/controllers/queueController.js
index 341c4b76b..3151816be 100644
--- a/Server/controllers/queueController.js
+++ b/Server/controllers/queueController.js
@@ -1,13 +1,15 @@
const { handleError } = require("./controllerUtils");
-
+const { errorMessages, successMessages } = require("../utils/messages");
const SERVICE_NAME = "JobQueueController";
const getMetrics = async (req, res, next) => {
try {
const metrics = await req.jobQueue.getMetrics();
- res
- .status(200)
- .json({ success: true, msg: "Metrics retrieved", data: metrics });
+ res.status(200).json({
+ success: true,
+ msg: successMessages.QUEUE_GET_METRICS,
+ data: metrics,
+ });
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMetrics"));
return;
@@ -17,7 +19,11 @@ const getMetrics = async (req, res, next) => {
const getJobs = async (req, res, next) => {
try {
const jobs = await req.jobQueue.getJobStats();
- return res.status(200).json({ jobs });
+ return res.status(200).json({
+ success: true,
+ msg: successMessages.QUEUE_GET_METRICS,
+ data: jobs,
+ });
} catch (error) {
next(handleError(error, SERVICE_NAME, "getJobs"));
return;
@@ -27,7 +33,10 @@ const getJobs = async (req, res, next) => {
const addJob = async (req, res, next) => {
try {
await req.jobQueue.addJob(Math.random().toString(36).substring(7));
- return res.send("Added job");
+ return res.status(200).json({
+ success: true,
+ msg: successMessages.QUEUE_ADD_JOB,
+ });
} catch (error) {
next(handleError(error, SERVICE_NAME, "addJob"));
return;
@@ -37,7 +46,9 @@ const addJob = async (req, res, next) => {
const obliterateQueue = async (req, res, next) => {
try {
await req.jobQueue.obliterate();
- return res.status(200).send("Obliterated queue");
+ return res
+ .status(200)
+ .json({ success: true, msg: successMessages.QUEUE_OBLITERATE });
} catch (error) {
next(handleError(error, SERVICE_NAME, "obliterateQueue"));
return;
diff --git a/Server/package-lock.json b/Server/package-lock.json
index bdd7db873..09793bb98 100644
--- a/Server/package-lock.json
+++ b/Server/package-lock.json
@@ -36,6 +36,7 @@
"devDependencies": {
"nodemon": "3.1.0",
"nyc": "17.1.0",
+ "proxyquire": "2.1.3",
"sinon": "19.0.2"
}
},
@@ -2802,6 +2803,20 @@
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
},
+ "node_modules/fill-keys": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz",
+ "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-object": "~1.0.1",
+ "merge-descriptors": "~1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3535,6 +3550,22 @@
"node": ">=8"
}
},
+ "node_modules/is-core-module": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
+ "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -3575,6 +3606,16 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-object": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz",
+ "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
@@ -4968,6 +5009,13 @@
"node": ">=10"
}
},
+ "node_modules/module-not-found-error": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz",
+ "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/mongodb": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.2.tgz",
@@ -5833,6 +5881,13 @@
"node": ">=8"
}
},
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
@@ -6545,6 +6600,18 @@
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
+ "node_modules/proxyquire": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz",
+ "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-keys": "^1.0.2",
+ "module-not-found-error": "^1.0.1",
+ "resolve": "^1.11.1"
+ }
+ },
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
@@ -6683,6 +6750,24 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -7242,6 +7327,19 @@
"node": ">=4"
}
},
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/svgo": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
diff --git a/Server/package.json b/Server/package.json
index 0ada3d893..cb6997132 100644
--- a/Server/package.json
+++ b/Server/package.json
@@ -38,6 +38,7 @@
"devDependencies": {
"nodemon": "3.1.0",
"nyc": "17.1.0",
+ "proxyquire": "2.1.3",
"sinon": "19.0.2"
}
}
diff --git a/Server/routes/inviteRoute.js b/Server/routes/inviteRoute.js
index 17744bf82..93e77e068 100644
--- a/Server/routes/inviteRoute.js
+++ b/Server/routes/inviteRoute.js
@@ -3,7 +3,7 @@ const { verifyJWT } = require("../middleware/verifyJWT");
const { isAllowed } = require("../middleware/isAllowed");
const {
- inviteController,
+ issueInvitation,
inviteVerifyController,
} = require("../controllers/inviteController");
@@ -11,8 +11,8 @@ router.post(
"/",
isAllowed(["admin", "superadmin"]),
verifyJWT,
- inviteController
+ issueInvitation
);
-router.post("/verify", inviteVerifyController);
+router.post("/verify", issueInvitation);
module.exports = router;
diff --git a/Server/tests/controllers/authController.test.js b/Server/tests/controllers/authController.test.js
index 5e5613972..842959415 100644
--- a/Server/tests/controllers/authController.test.js
+++ b/Server/tests/controllers/authController.test.js
@@ -1,4 +1,5 @@
const {
+ issueToken,
registerUser,
loginUser,
editUser,
@@ -12,11 +13,45 @@ const {
const jwt = require("jsonwebtoken");
const { errorMessages, successMessages } = require("../../utils/messages");
const sinon = require("sinon");
+const logger = require("../../utils/logger");
+
+describe("Auth Controller - issueToken", () => {
+ it("should reject with an error if jwt.sign fails", () => {
+ const error = new Error("jwt.sign error");
+ stub = sinon.stub(jwt, "sign").throws(error);
+ const payload = { id: "123" };
+ const appSettings = { jwtSecret: "my_secret" };
+ expect(() => issueToken(payload, appSettings)).to.throw(error);
+ stub.restore();
+ });
+
+ it("should return a token if jwt.sign is successful and appSettings.jtwTTL is not defined", () => {
+ const payload = { id: "123" };
+ const appSettings = { jwtSecret: "my_secret" };
+ const token = issueToken(payload, appSettings);
+ expect(token).to.be.a("string");
+ });
+
+ it("should return a token if jwt.sign is successful and appSettings.jwtTTL is defined", () => {
+ const payload = { id: "123" };
+ const appSettings = { jwtSecret: "my_secret", jwtTTL: "1s" };
+ const token = issueToken(payload, appSettings);
+ expect(token).to.be.a("string");
+ });
+});
describe("Auth Controller - registerUser", () => {
- // Set up test
beforeEach(() => {
req = {
+ body: {
+ firstName: "firstname",
+ lastName: "lastname",
+ email: "test@test.com",
+ password: "Uptime1!",
+ role: ["admin"],
+ teamId: "123",
+ inviteToken: "invite",
+ },
db: {
checkSuperadmin: sinon.stub(),
getInviteTokenAndDelete: sinon.stub(),
@@ -29,7 +64,7 @@ describe("Auth Controller - registerUser", () => {
}),
},
emailService: {
- buildAndSendEmail: sinon.stub().returns(Promise.resolve()),
+ buildAndSendEmail: sinon.stub(),
},
file: {},
};
@@ -38,72 +73,97 @@ describe("Auth Controller - registerUser", () => {
json: sinon.stub(),
};
next = sinon.stub();
+ sinon.stub(logger, "error");
+ });
+ afterEach(() => {
+ sinon.restore();
});
- it("should register a valid user", async () => {
- req.body = {
- firstName: "John",
- lastName: "Doe",
- email: "john.doe@example.com",
- password: "Uptime1!",
- inviteToken: "someToken",
- role: ["user"],
- teamId: "123",
- };
- req.db.checkSuperadmin.resolves(false);
- req.db.insertUser.resolves({
- _id: "123",
- _doc: {
- firstName: "John",
- lastName: "Doe",
- email: "john.doe@example.com",
- },
- });
+ it("should reject with an error if body validation fails", async () => {
+ 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 () => {
+ req.db.checkSuperadmin.rejects(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 () => {
+ req.db.checkSuperadmin.resolves(true);
+ req.db.getInviteTokenAndDelete.rejects(
+ new Error("getInviteTokenAndDelete error")
+ );
+ await registerUser(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal(
+ "getInviteTokenAndDelete error"
+ );
+ });
+
+ it("should reject with an error if updateAppSettings fails", async () => {
+ req.db.checkSuperadmin.resolves(false);
+ req.db.updateAppSettings.rejects(new Error("updateAppSettings error"));
+ await registerUser(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 insertUser fails", async () => {
+ req.db.checkSuperadmin.resolves(false);
+ req.db.updateAppSettings.resolves();
+ req.db.insertUser.rejects(new Error("insertUser error"));
+ await registerUser(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("insertUser error");
+ });
+
+ it("should reject with an error if settingsService.getSettings fails", async () => {
+ req.db.checkSuperadmin.resolves(false);
+ req.db.updateAppSettings.resolves();
+ req.db.insertUser.resolves({ _id: "123" });
+ req.settingsService.getSettings.rejects(
+ new Error("settingsService.getSettings error")
+ );
+ await registerUser(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal(
+ "settingsService.getSettings error"
+ );
+ });
+
+ it("should log an error if emailService.buildAndSendEmail fails", async () => {
+ req.db.checkSuperadmin.resolves(false);
+ req.db.updateAppSettings.resolves();
+ req.db.insertUser.returns({ _id: "123" });
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.emailService.buildAndSendEmail.rejects(new Error("emailService error"));
+ await registerUser(req, res, next);
+ expect(logger.error.calledOnce).to.be.true;
+ expect(logger.error.firstCall.args[1].error).to.equal("emailService error");
+ });
+ it("should return a success message and data if all operations are successful", async () => {
+ const user = { _id: "123" };
+ req.db.checkSuperadmin.resolves(false);
+ req.db.updateAppSettings.resolves();
+ req.db.insertUser.returns(user);
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.emailService.buildAndSendEmail.resolves("message-id");
await registerUser(req, res, next);
expect(res.status.calledWith(200)).to.be.true;
expect(
- res.json.calledWith(
- sinon.match({
- success: true,
- msg: sinon.match.string,
- data: {
- user: sinon.match.object,
- token: sinon.match.string,
- },
- })
- )
+ res.json.calledWith({
+ success: true,
+ msg: successMessages.AUTH_CREATE_USER,
+ data: { user, token: sinon.match.string },
+ })
).to.be.true;
expect(next.notCalled).to.be.true;
});
- it("should reject a user with an invalid password", async () => {
- req.body = {
- firstName: "John",
- lastName: "Doe",
- email: "john.doe@example.com",
- password: "bad_password",
- inviteToken: "someToken",
- role: ["user"],
- teamId: "123",
- };
- 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 a user with an invalid role", async () => {
- req.body = {
- firstName: "John",
- lastName: "Doe",
- email: "john.doe@example.com",
- password: "Uptime1!",
- inviteToken: "someToken",
- role: ["superman"],
- teamId: "123",
- };
- await registerUser(req, res, next);
- expect(next.firstCall.args[0]).to.be.an("error");
- expect(next.firstCall.args[0].status).to.equal(422);
- });
});
describe("Auth Controller - loginUser", () => {
@@ -131,6 +191,20 @@ describe("Auth Controller - loginUser", () => {
comparePassword: sinon.stub(),
};
});
+ it("should reject with an error if validation fails", async () => {
+ 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 () => {
+ 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 () => {
req.db.getUserByEmail.resolves(user);
user.comparePassword.resolves(true);
@@ -186,10 +260,63 @@ describe("Auth Controller - editUser", async () => {
json: sinon.stub(),
};
next = sinon.stub();
+ stub = sinon.stub(jwt, "verify").returns({ email: "test@example.com" });
+ });
+ afterEach(() => {
+ sinon.restore();
+ stub.restore();
+ });
+
+ it("should reject with an error if param validation fails", async () => {
+ 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 () => {
+ 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 () => {
+ 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 () => {
+ 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 () => {
+ req.db.getUserByEmail.returns({
+ comparePassword: sinon.stub().rejects(new Error("Bad Password Match")),
+ });
+ await editUser(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Bad Password Match");
+ });
+
+ it("should reject with an error if user.comparePassword returns false", async () => {
+ req.db.getUserByEmail.returns({
+ comparePassword: sinon.stub().returns(false),
+ });
+ await editUser(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(403);
+ expect(next.firstCall.args[0].message).to.equal(
+ errorMessages.AUTH_INCORRECT_PASSWORD
+ );
});
it("should edit a user if it receives a proper request", async () => {
- sinon.stub(jwt, "verify").returns({ email: "test@example.com" });
const user = {
comparePassword: sinon.stub().resolves(true),
};
@@ -211,6 +338,24 @@ 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 () => {
+ req.body.password = undefined;
+ req.body.newPassword = undefined;
+ req.db.getUserByEmail.resolves(user);
+ req.db.updateUser.resolves({ email: "test@example.com" });
+
+ await editUser(req, res, next);
+ expect(res.status.calledWith(200)).to.be.true;
+ expect(
+ res.json.calledWith({
+ success: true,
+ msg: successMessages.AUTH_UPDATE_USER,
+ data: { email: "test@example.com" },
+ })
+ ).to.be.true;
+ expect(next.notCalled).to.be.true;
+ });
+
it("should reject an edit request if password format is incorrect", async () => {
req.body = { password: "bad_password", newPassword: "bad_password" };
const user = {
@@ -239,6 +384,13 @@ describe("Auth Controller - checkSuperadminExists", async () => {
next = sinon.stub();
});
+ it("should reject with an error if checkSuperadmin fails", async () => {
+ 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 () => {
req.db.checkSuperadmin.resolves(true);
await checkSuperadminExists(req, res, next);
@@ -289,6 +441,30 @@ describe("Auth Controller - requestRecovery", async () => {
};
next = sinon.stub();
});
+
+ it("should reject with an error if validation fails", async () => {
+ 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 () => {
+ 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 () => {
+ req.db.getUserByEmail.resolves(null);
+ await requestRecovery(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ // expect(next.firstCall.args[0].message).to.equal(
+ // errorMessages.FRIENDLY_ERROR
+ // );
+ });
+
it("should throw an error if the email is not provided", async () => {
req.body = {};
await requestRecovery(req, res, next);
@@ -344,15 +520,24 @@ describe("Auth Controller - validateRecovery", async () => {
next = sinon.stub();
});
- it("should call next with a validation error if the token is invalid", async () => {
- req = {
- body: {},
- };
+ it("should reject with an error if validation fails", async () => {
+ 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 () => {
+ 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 () => {
req.db.validateRecoveryToken.resolves();
await validateRecovery(req, res, next);
@@ -391,14 +576,23 @@ describe("Auth Controller - resetPassword", async () => {
};
handleValidationError = sinon.stub();
handleError = sinon.stub();
- issueToken = sinon.stub();
});
- it("should call next with a validation error if the password is invalid", async () => {
+ it("should reject with an error if validation fails", async () => {
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 () => {
+ const error = new Error("resetPassword error");
+ newPasswordValidation.validateAsync.resolves();
+ req.db.resetPassword.rejects(error);
+ await resetPassword(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("resetPassword error");
+ });
+
it("should reset password successfully", async () => {
const user = { _doc: {} };
const appSettings = { jwtSecret: "my_secret" };
@@ -407,7 +601,6 @@ describe("Auth Controller - resetPassword", async () => {
newPasswordValidation.validateAsync.resolves();
req.db.resetPassword.resolves(user);
req.settingsService.getSettings.resolves(appSettings);
- issueToken.returns(token);
await resetPassword(req, res, next);
diff --git a/Server/tests/controllers/checkController.test.js b/Server/tests/controllers/checkController.test.js
new file mode 100644
index 000000000..a3060d60a
--- /dev/null
+++ b/Server/tests/controllers/checkController.test.js
@@ -0,0 +1,370 @@
+const {
+ createCheck,
+ getChecks,
+ getTeamChecks,
+ deleteChecks,
+ deleteChecksByTeamId,
+ updateChecksTTL,
+} = require("../../controllers/checkController");
+const jwt = require("jsonwebtoken");
+const { errorMessages, successMessages } = require("../../utils/messages");
+const sinon = require("sinon");
+
+describe("Check Controller - createCheck", () => {
+ beforeEach(() => {
+ req = {
+ params: {},
+ body: {},
+ db: {
+ createCheck: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ handleError = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore(); // Restore the original methods after each test
+ });
+
+ it("should reject with a validation if params are invalid", async () => {
+ 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 () => {
+ req.params = {
+ monitorId: "monitorId",
+ };
+ 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 call next with error if data retrieval fails", async () => {
+ req.params = {
+ monitorId: "monitorId",
+ };
+ req.body = {
+ monitorId: "monitorId",
+ status: true,
+ responseTime: 100,
+ statusCode: 200,
+ message: "message",
+ };
+ req.db.createCheck.rejects(new Error("error"));
+ await createCheck(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ });
+
+ it("should return a success message if check is created", async () => {
+ req.params = {
+ monitorId: "monitorId",
+ };
+ req.db.createCheck.resolves({ id: "123" });
+ req.body = {
+ monitorId: "monitorId",
+ status: true,
+ responseTime: 100,
+ statusCode: 200,
+ message: "message",
+ };
+ await createCheck(req, res, next);
+ expect(res.status.calledWith(200)).to.be.true;
+ expect(
+ res.json.calledWith({
+ success: true,
+ msg: successMessages.CHECK_CREATE,
+ data: { id: "123" },
+ })
+ ).to.be.true;
+ expect(next.notCalled).to.be.true;
+ });
+});
+
+describe("Check Controller - getChecks", () => {
+ beforeEach(() => {
+ req = {
+ params: {},
+ query: {},
+ db: {
+ getChecks: sinon.stub(),
+ getChecksCount: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with a validation error if params are invalid", async () => {
+ 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 () => {
+ req.params = {
+ monitorId: "monitorId",
+ };
+ req.db.getChecks.resolves([{ id: "123" }]);
+ req.db.getChecksCount.resolves(1);
+ await getChecks(req, res, next);
+ expect(res.status.calledWith(200)).to.be.true;
+ expect(
+ res.json.calledWith({
+ success: true,
+ msg: successMessages.CHECK_GET,
+ data: { checksCount: 1, checks: [{ id: "123" }] },
+ })
+ ).to.be.true;
+ expect(next.notCalled).to.be.true;
+ });
+
+ it("should call next with error if data retrieval fails", async () => {
+ req.params = {
+ monitorId: "monitorId",
+ };
+ req.db.getChecks.rejects(new Error("error"));
+ await getChecks(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ });
+});
+
+describe("Check Controller - getTeamChecks", () => {
+ beforeEach(() => {
+ req = {
+ params: {},
+ query: {},
+ db: {
+ getTeamChecks: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with a validation error if params are invalid", async () => {
+ 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 () => {
+ req.params = { teamId: "1" };
+ const checkData = [{ id: 1, name: "Check 1" }];
+ req.db.getTeamChecks.resolves(checkData);
+
+ await getTeamChecks(req, res, next);
+ expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true;
+ expect(res.status.calledOnceWith(200)).to.be.true;
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.CHECK_GET,
+ data: checkData,
+ })
+ ).to.be.true;
+ });
+
+ it("should call next with error if data retrieval fails", async () => {
+ req.params = { teamId: "1" };
+ req.db.getTeamChecks.rejects(new Error("Retrieval Error"));
+ await getTeamChecks(req, res, next);
+ expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true;
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(res.status.notCalled).to.be.true;
+ expect(res.json.notCalled).to.be.true;
+ });
+});
+
+describe("Check Controller - deleteChecks", () => {
+ beforeEach(() => {
+ req = {
+ params: {},
+ db: {
+ deleteChecks: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with an error if param validation fails", async () => {
+ 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 () => {
+ req.params = { monitorId: "1" };
+ req.db.deleteChecks.rejects(new Error("Deletion Error"));
+ await deleteChecks(req, res, next);
+ expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true;
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(res.status.notCalled).to.be.true;
+ expect(res.json.notCalled).to.be.true;
+ });
+
+ it("should delete checks successfully", async () => {
+ req.params = { monitorId: "123" };
+ req.db.deleteChecks.resolves(1);
+ await deleteChecks(req, res, next);
+ expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true;
+ expect(res.status.calledOnceWith(200)).to.be.true;
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.CHECK_DELETE,
+ data: { deletedCount: 1 },
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Check Controller - deleteChecksByTeamId", () => {
+ beforeEach(() => {
+ req = {
+ params: {},
+ db: {
+ deleteChecksByTeamId: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with an error if param validation fails", async () => {
+ 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 () => {
+ req.params = { teamId: "1" };
+ req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error"));
+ await deleteChecksByTeamId(req, res, next);
+ expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be
+ .true;
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(res.status.notCalled).to.be.true;
+ expect(res.json.notCalled).to.be.true;
+ });
+
+ it("should delete checks successfully", async () => {
+ req.params = { teamId: "123" };
+ req.db.deleteChecksByTeamId.resolves(1);
+ await deleteChecksByTeamId(req, res, next);
+ expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be
+ .true;
+ expect(res.status.calledOnceWith(200)).to.be.true;
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.CHECK_DELETE,
+ data: { deletedCount: 1 },
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Check Controller - updateCheckTTL", () => {
+ beforeEach(() => {
+ stub = sinon.stub(jwt, "verify").callsFake(() => {
+ return { teamId: "123" };
+ });
+
+ req = {
+ body: {},
+ headers: { authorization: "Bearer token" },
+ settingsService: {
+ getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }),
+ },
+ db: {
+ updateChecksTTL: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ stub.restore();
+ });
+
+ it("should reject if body validation fails", async () => {
+ 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 () => {
+ stub.restore();
+ req.body = {
+ ttl: 1,
+ };
+ await updateChecksTTL(req, res, next);
+ expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
+ });
+
+ it("should call next with error if data retrieval fails", async () => {
+ req.body = {
+ ttl: 1,
+ };
+ req.db.updateChecksTTL.rejects(new Error("Update Error"));
+ await updateChecksTTL(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ });
+
+ it("should update TTL successfully", async () => {
+ req.body = {
+ ttl: 1,
+ };
+ req.db.updateChecksTTL.resolves();
+ await updateChecksTTL(req, res, next);
+ expect(req.db.updateChecksTTL.calledOnceWith("123", 1 * 86400)).to.be.true;
+ expect(res.status.calledOnceWith(200)).to.be.true;
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.CHECK_UPDATE_TTL,
+ })
+ ).to.be.true;
+ });
+});
diff --git a/Server/tests/controllers/controllerUtils.test.js b/Server/tests/controllers/controllerUtils.test.js
new file mode 100644
index 000000000..71d134bb6
--- /dev/null
+++ b/Server/tests/controllers/controllerUtils.test.js
@@ -0,0 +1,105 @@
+const {
+ handleValidationError,
+ handleError,
+} = require("../../controllers/controllerUtils");
+
+describe("controllerUtils - handleValidationError", () => {
+ it("should set status to 422", () => {
+ const error = {};
+ const serviceName = "TestService";
+ const result = handleValidationError(error, serviceName);
+ expect(result.status).to.equal(422);
+ });
+
+ it("should set service to the provided serviceName", () => {
+ 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", () => {
+ const error = {
+ details: [{ message: "Detail message" }],
+ };
+ const serviceName = "TestService";
+ const result = handleValidationError(error, serviceName);
+ expect(result.message).to.equal("Detail message");
+ });
+
+ it("should set message to error.message if error.details is not present", () => {
+ const error = {
+ message: "Error message",
+ };
+ const serviceName = "TestService";
+ const result = handleValidationError(error, serviceName);
+ expect(result.message).to.equal("Error message");
+ });
+
+ it('should set message to "Validation Error" if neither error.details nor error.message is present', () => {
+ const error = {};
+ const serviceName = "TestService";
+ const result = handleValidationError(error, serviceName);
+ expect(result.message).to.equal("Validation Error");
+ });
+});
+
+describe("handleError", () => {
+ it("should set stats to the provided status if error.code is undefined", () => {
+ const error = {};
+ const serviceName = "TestService";
+ const method = "testMethod";
+ const status = 400;
+ const result = handleError(error, serviceName, method, status);
+ expect(result.status).to.equal(status);
+ });
+
+ it("should not overwrite error.code if it is already defined", () => {
+ const error = { status: 404 };
+ const serviceName = "TestService";
+ const method = "testMethod";
+ const status = 400;
+ const result = handleError(error, serviceName, method, status);
+ expect(result.status).to.equal(404);
+ });
+
+ it("should set service to the provided serviceName if error.service is undefined", () => {
+ const error = {};
+ const serviceName = "TestService";
+ const method = "testMethod";
+ const result = handleError(error, serviceName, method);
+ expect(result.service).to.equal(serviceName);
+ });
+
+ it("should not overwrite error.service if it is already defined", () => {
+ const error = { service: "ExistingService" };
+ const serviceName = "TestService";
+ const method = "testMethod";
+ const result = handleError(error, serviceName, method);
+ expect(result.service).to.equal("ExistingService");
+ });
+
+ it("should set method to the provided method if error.method is undefined", () => {
+ const error = {};
+ const serviceName = "TestService";
+ const method = "testMethod";
+ const result = handleError(error, serviceName, method);
+ expect(result.method).to.equal(method);
+ });
+
+ it("should not overwrite error.method if it is already defined", () => {
+ const error = { method: "existingMethod" };
+ const serviceName = "TestService";
+ const method = "testMethod";
+ const result = handleError(error, serviceName, method);
+ expect(result.method).to.equal("existingMethod");
+ });
+
+ it("should set code to 500 if error.code is undefined and no code is provided", () => {
+ const error = {};
+ const serviceName = "TestService";
+ const method = "testMethod";
+ const result = handleError(error, serviceName, method);
+ expect(result.status).to.equal(500);
+ });
+});
diff --git a/Server/tests/controllers/inviteController.test.js b/Server/tests/controllers/inviteController.test.js
new file mode 100644
index 000000000..31085c40f
--- /dev/null
+++ b/Server/tests/controllers/inviteController.test.js
@@ -0,0 +1,202 @@
+const {
+ issueInvitation,
+ inviteVerifyController,
+} = require("../../controllers/inviteController");
+const jwt = require("jsonwebtoken");
+const { errorMessages, successMessages } = require("../../utils/messages");
+const sinon = require("sinon");
+const joi = require("joi");
+describe("inviteController - issueInvitation", () => {
+ beforeEach(() => {
+ req = {
+ headers: { authorization: "Bearer token" },
+ body: {
+ email: "test@test.com",
+ role: ["admin"],
+ teamId: "123",
+ },
+ db: { requestInviteToken: sinon.stub() },
+ settingsService: { getSettings: sinon.stub() },
+ emailService: { buildAndSendEmail: sinon.stub() },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with an error if role validation fails", async () => {
+ stub = sinon.stub(jwt, "decode").callsFake(() => {
+ return { role: ["bad_role"], firstname: "first_name", teamId: "1" };
+ });
+ await issueInvitation(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError);
+ expect(next.firstCall.args[0].status).to.equal(422);
+ stub.restore();
+ });
+
+ it("should reject with an error if body validation fails", async () => {
+ stub = sinon.stub(jwt, "decode").callsFake(() => {
+ return { role: ["admin"], firstname: "first_name", teamId: "1" };
+ });
+ req.body = {};
+ await issueInvitation(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ stub.restore();
+ });
+
+ it("should reject with an error if DB operations fail", async () => {
+ stub = sinon.stub(jwt, "decode").callsFake(() => {
+ return { role: ["admin"], firstname: "first_name", teamId: "1" };
+ });
+ req.db.requestInviteToken.throws(new Error("DB error"));
+ await issueInvitation(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ stub.restore();
+ });
+
+ it("should send an invite successfully", async () => {
+ const token = "token";
+ const decodedToken = {
+ role: "admin",
+ firstname: "John",
+ teamId: "team123",
+ };
+ const inviteToken = { token: "inviteToken" };
+ const clientHost = "http://localhost";
+
+ stub = sinon.stub(jwt, "decode").callsFake(() => {
+ return decodedToken;
+ });
+ req.db.requestInviteToken.resolves(inviteToken);
+ req.settingsService.getSettings.returns({ clientHost });
+ req.emailService.buildAndSendEmail.resolves();
+ await issueInvitation(req, res, next);
+ expect(res.status.calledWith(200)).to.be.true;
+ expect(
+ res.json.calledWith({
+ success: true,
+ msg: "Invite sent",
+ data: inviteToken,
+ })
+ ).to.be.true;
+ stub.restore();
+ });
+
+ it("should send an email successfully", async () => {
+ const token = "token";
+ const decodedToken = {
+ role: "admin",
+ firstname: "John",
+ teamId: "team123",
+ };
+ const inviteToken = { token: "inviteToken" };
+ const clientHost = "http://localhost";
+
+ stub = sinon.stub(jwt, "decode").callsFake(() => {
+ return decodedToken;
+ });
+ req.db.requestInviteToken.resolves(inviteToken);
+ req.settingsService.getSettings.returns({ clientHost });
+ req.emailService.buildAndSendEmail.resolves();
+
+ await issueInvitation(req, res, next);
+ expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true;
+ expect(
+ req.emailService.buildAndSendEmail.calledWith(
+ "employeeActivationTemplate",
+ {
+ name: "John",
+ link: "http://localhost/register/inviteToken",
+ },
+ "test@test.com",
+ "Welcome to Uptime Monitor"
+ )
+ ).to.be.true;
+ stub.restore();
+ });
+
+ it("should continue executing if sending an email fails", async () => {
+ const token = "token";
+ req.emailService.buildAndSendEmail.rejects(new Error("Email error"));
+ const decodedToken = {
+ role: "admin",
+ firstname: "John",
+ teamId: "team123",
+ };
+ const inviteToken = { token: "inviteToken" };
+ const clientHost = "http://localhost";
+
+ stub = sinon.stub(jwt, "decode").callsFake(() => {
+ return decodedToken;
+ });
+ req.db.requestInviteToken.resolves(inviteToken);
+ req.settingsService.getSettings.returns({ clientHost });
+ await issueInvitation(req, res, next);
+ expect(res.status.calledWith(200)).to.be.true;
+ expect(
+ res.json.calledWith({
+ success: true,
+ msg: "Invite sent",
+ data: inviteToken,
+ })
+ ).to.be.true;
+ stub.restore();
+ });
+});
+
+describe("inviteController - inviteVerifyController", () => {
+ beforeEach(() => {
+ req = {
+ body: { token: "token" },
+ db: {
+ getInviteToken: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with an error if body validation fails", async () => {
+ 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 () => {
+ 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 () => {
+ req.db.getInviteToken.resolves({ invite: "data" });
+ await inviteVerifyController(req, res, next);
+ expect(res.status.calledWith(200)).to.be.true;
+ expect(
+ res.json.calledWith({
+ status: "success",
+ msg: "Invite verified",
+ data: { invite: "data" },
+ })
+ ).to.be.true;
+ expect(next.called).to.be.false;
+ });
+});
diff --git a/Server/tests/controllers/maintenanceWindowController.test.js b/Server/tests/controllers/maintenanceWindowController.test.js
new file mode 100644
index 000000000..5bc3a906e
--- /dev/null
+++ b/Server/tests/controllers/maintenanceWindowController.test.js
@@ -0,0 +1,410 @@
+const {
+ createMaintenanceWindows,
+ getMaintenanceWindowById,
+ getMaintenanceWindowsByTeamId,
+ getMaintenanceWindowsByMonitorId,
+ deleteMaintenanceWindow,
+ editMaintenanceWindow,
+} = require("../../controllers/maintenanceWindowController");
+
+const jwt = require("jsonwebtoken");
+const { errorMessages, successMessages } = require("../../utils/messages");
+const sinon = require("sinon");
+
+describe("maintenanceWindowController - createMaintenanceWindows", () => {
+ beforeEach(() => {
+ req = {
+ body: {
+ monitors: ["66ff52e7c5911c61698ac724"],
+ name: "window",
+ active: true,
+ start: "2024-10-11T05:27:13.747Z",
+ end: "2024-10-11T05:27:14.747Z",
+ repeat: "123",
+ },
+ headers: {
+ authorization: "Bearer token",
+ },
+ settingsService: {
+ getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
+ },
+ db: {
+ createMaintenanceWindow: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with an error if body validation fails", async () => {
+ stub = sinon.stub(jwt, "verify").callsFake(() => {
+ return { teamId: "123" };
+ });
+ req.body = {};
+ await createMaintenanceWindows(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ stub.restore();
+ });
+
+ it("should reject with an error if jwt.verify fails", async () => {
+ 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 () => {
+ stub = sinon.stub(jwt, "verify").callsFake(() => {
+ return { teamId: "123" };
+ });
+ req.db.createMaintenanceWindow.throws(new Error("DB error"));
+ await createMaintenanceWindows(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ stub.restore();
+ });
+ it("should return success message if all operations are successful", async () => {
+ stub = sinon.stub(jwt, "verify").callsFake(() => {
+ return { teamId: "123" };
+ });
+ await createMaintenanceWindows(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(201);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MAINTENANCE_WINDOW_CREATE,
+ })
+ ).to.be.true;
+ stub.restore();
+ });
+ it("should return success message if all operations are successful with active set to undefined", async () => {
+ req.body.active = undefined;
+ stub = sinon.stub(jwt, "verify").callsFake(() => {
+ return { teamId: "123" };
+ });
+ await createMaintenanceWindows(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(201);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MAINTENANCE_WINDOW_CREATE,
+ })
+ ).to.be.true;
+ stub.restore();
+ });
+});
+
+describe("maintenanceWindowController - getMaintenanceWindowById", () => {
+ beforeEach(() => {
+ req = {
+ body: {},
+ params: {
+ id: "123",
+ },
+ headers: {
+ authorization: "Bearer token",
+ },
+ settingsService: {
+ getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
+ },
+ db: {
+ getMaintenanceWindowById: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ it("should reject if param validation fails", async () => {
+ 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 () => {
+ 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 () => {
+ req.db.getMaintenanceWindowById.returns({ id: "123" });
+ await getMaintenanceWindowById(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID,
+ data: { id: "123" },
+ })
+ ).to.be.true;
+ });
+});
+
+describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
+ beforeEach(() => {
+ req = {
+ body: {},
+ params: {},
+ query: {},
+ headers: {
+ authorization: "Bearer token",
+ },
+ settingsService: {
+ getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
+ },
+ db: {
+ getMaintenanceWindowsByTeamId: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ it("should reject if query validation fails", async () => {
+ req.query = {
+ invalid: 1,
+ };
+ await getMaintenanceWindowsByTeamId(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 jwt.verify fails", async () => {
+ 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 () => {
+ stub = sinon.stub(jwt, "verify").callsFake(() => {
+ return { teamId: "123" };
+ });
+ req.db.getMaintenanceWindowsByTeamId.throws(new Error("DB error"));
+ await getMaintenanceWindowsByTeamId(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ stub.restore();
+ });
+
+ it("should return success message with data if all operations are successful", async () => {
+ stub = sinon.stub(jwt, "verify").callsFake(() => {
+ return { teamId: "123" };
+ });
+ req.db.getMaintenanceWindowsByTeamId.returns([{ id: "123" }]);
+ await getMaintenanceWindowsByTeamId(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM,
+ data: [{ id: jwt.verify().teamId }],
+ })
+ ).to.be.true;
+ stub.restore();
+ });
+});
+
+describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () => {
+ beforeEach(() => {
+ req = {
+ body: {},
+ params: {
+ monitorId: "123",
+ },
+ query: {},
+ headers: {
+ authorization: "Bearer token",
+ },
+ settingsService: {
+ getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
+ },
+ db: {
+ getMaintenanceWindowsByMonitorId: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject if param validation fails", async () => {
+ 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 () => {
+ 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 () => {
+ const data = [{ monitorId: "123" }];
+ req.db.getMaintenanceWindowsByMonitorId.returns(data);
+ await getMaintenanceWindowsByMonitorId(req, res, next);
+ expect(
+ req.db.getMaintenanceWindowsByMonitorId.calledOnceWith(
+ req.params.monitorId
+ )
+ );
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR,
+ data: data,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("maintenanceWindowController - deleteMaintenanceWindow", () => {
+ beforeEach(() => {
+ req = {
+ body: {},
+ params: {
+ id: "123",
+ },
+ query: {},
+ headers: {
+ authorization: "Bearer token",
+ },
+ settingsService: {
+ getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
+ },
+ db: {
+ deleteMaintenanceWindowById: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject if param validation fails", async () => {
+ 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 () => {
+ 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 () => {
+ await deleteMaintenanceWindow(req, res, next);
+ expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id));
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MAINTENANCE_WINDOW_DELETE,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("maintenanceWindowController - editMaintenanceWindow", () => {
+ beforeEach(() => {
+ req = {
+ body: {
+ active: true,
+ name: "test",
+ },
+ params: {
+ id: "123",
+ },
+ query: {},
+ headers: {
+ authorization: "Bearer token",
+ },
+ settingsService: {
+ getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }),
+ },
+ db: {
+ editMaintenanceWindowById: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject if param validation fails", async () => {
+ 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 () => {
+ 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 () => {
+ 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 () => {
+ const data = { id: "123" };
+ req.db.editMaintenanceWindowById.returns(data);
+
+ await editMaintenanceWindow(req, res, next);
+ expect(
+ req.db.editMaintenanceWindowById.calledOnceWith(req.params.id, req.body)
+ );
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MAINTENANCE_WINDOW_EDIT,
+ data: data,
+ })
+ ).to.be.true;
+ });
+});
diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js
new file mode 100644
index 000000000..e2855e81b
--- /dev/null
+++ b/Server/tests/controllers/monitorController.test.js
@@ -0,0 +1,1010 @@
+const {
+ getAllMonitors,
+ getMonitorStatsById,
+ getMonitorCertificate,
+ getMonitorById,
+ getMonitorsAndSummaryByTeamId,
+ getMonitorsByTeamId,
+ createMonitor,
+ deleteMonitor,
+ deleteAllMonitors,
+ editMonitor,
+ pauseMonitor,
+ addDemoMonitors,
+} = require("../../controllers/monitorController");
+const jwt = require("jsonwebtoken");
+const proxyquire = require("proxyquire");
+const sinon = require("sinon");
+const { errorMessages, successMessages } = require("../../utils/messages");
+const sslCheckerStub = sinon.stub();
+const monitorController = proxyquire("../../controllers/monitorController", {
+ "ssl-checker": sslCheckerStub,
+});
+const logger = require("../../utils/logger");
+const SERVICE_NAME = "monitorController";
+
+describe("Monitor Controller - getAllMonitors", () => {
+ beforeEach(() => {
+ req = {
+ params: {},
+ query: {},
+ body: {},
+ db: {
+ getAllMonitors: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if DB operations fail", async () => {
+ req.db.getAllMonitors.throws(new Error("DB error"));
+ await getAllMonitors(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+
+ it("should return success message and data if all operations succeed", async () => {
+ const data = [{ monitor: "data" }];
+ req.db.getAllMonitors.returns(data);
+ await getAllMonitors(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_GET_ALL,
+ data: data,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - getMonitorStatsById", () => {
+ beforeEach(() => {
+ req = {
+ params: {
+ monitorId: "123",
+ },
+ query: {},
+ body: {},
+ db: {
+ getMonitorStatsById: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with an error if param validation fails", async () => {
+ req.params = {};
+ await getMonitorStatsById(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if query validation fails", async () => {
+ req.query = { invalid: 1 };
+ await getMonitorStatsById(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if DB operations fail", async () => {
+ req.db.getMonitorStatsById.throws(new Error("DB error"));
+ await getMonitorStatsById(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should return success message and data if all operations succeed", async () => {
+ const data = [{ monitorStats: "data" }];
+ req.db.getMonitorStatsById.returns(data);
+ await getMonitorStatsById(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_STATS_BY_ID,
+ data: data,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - getMonitorCertificate", () => {
+ beforeEach(() => {
+ req = {
+ params: {
+ monitorId: "123",
+ },
+ query: {},
+ body: {},
+ db: {
+ getMonitorById: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with an error if param validation fails", async () => {
+ req.params = {};
+ await getMonitorCertificate(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if creating a URL fails", async () => {
+ req.db.getMonitorById.returns(null);
+ await getMonitorCertificate(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0]).to.be.instanceOf(TypeError);
+ });
+ it("should reject with an error if SSL checker fails", async () => {
+ req.db.getMonitorById.returns({ url: "https://example.com" });
+ sslCheckerStub.throws(new Error("SSL checker error"));
+ await monitorController.getMonitorCertificate(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("SSL checker error");
+ });
+ it("should return success message and data if all operations succeed", async () => {
+ req.db.getMonitorById.returns({ url: "https://example.com" });
+ const certificate = { validTo: 1 };
+ sslCheckerStub.returns(certificate);
+ await monitorController.getMonitorCertificate(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_CERTIFICATE,
+ data: {
+ certificateDate: new Date(certificate.validTo).toLocaleDateString(),
+ },
+ })
+ ).to.be.true;
+ });
+ it("should return success message and N/A if certificate is not found", async () => {
+ req.db.getMonitorById.returns({ url: "https://example.com" });
+ sslCheckerStub.returns(null);
+ await monitorController.getMonitorCertificate(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_CERTIFICATE,
+ data: {
+ certificateDate: "N/A",
+ },
+ })
+ ).to.be.true;
+ });
+
+ it("should return success message and N/A if certificate doesn't have validTo", async () => {
+ req.db.getMonitorById.returns({ url: "https://example.com" });
+ sslCheckerStub.returns({ valid: 1 });
+ await monitorController.getMonitorCertificate(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_CERTIFICATE,
+ data: {
+ certificateDate: "N/A",
+ },
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - getMonitorById", () => {
+ beforeEach(() => {
+ req = {
+ params: {
+ monitorId: "123",
+ },
+ query: {},
+ body: {},
+ db: {
+ getMonitorById: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if param validation fails", async () => {
+ req.params = {};
+ await getMonitorById(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if query param validation fails", async () => {
+ req.query = { invalid: 1 };
+ await getMonitorById(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if DB operations fail", async () => {
+ req.db.getMonitorById.throws(new Error("DB error"));
+ await getMonitorById(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should return 404 if a monitor is not found", async () => {
+ req.db.getMonitorById.returns(null);
+ await getMonitorById(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(404);
+ });
+ it("should return success message and data if all operations succeed", async () => {
+ const data = { monitor: "data" };
+ req.db.getMonitorById.returns(data);
+ await getMonitorById(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_GET_BY_ID,
+ data: data,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - getMonitorsAndSummaryByTeamId", () => {
+ beforeEach(() => {
+ req = {
+ params: {
+ teamId: "123",
+ },
+ query: {},
+ body: {},
+ db: {
+ getMonitorsAndSummaryByTeamId: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with an error if param validation fails", async () => {
+ req.params = {};
+ await getMonitorsAndSummaryByTeamId(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if query validation fails", async () => {
+ req.query = { invalid: 1 };
+ await getMonitorsAndSummaryByTeamId(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if DB operations fail", async () => {
+ req.db.getMonitorsAndSummaryByTeamId.throws(new Error("DB error"));
+ await getMonitorsAndSummaryByTeamId(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should return success message and data if all operations succeed", async () => {
+ const data = { monitors: "data", summary: "data" };
+ req.db.getMonitorsAndSummaryByTeamId.returns(data);
+ await getMonitorsAndSummaryByTeamId(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId),
+ data: data,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - getMonitorsByTeamId", () => {
+ beforeEach(() => {
+ req = {
+ params: {
+ teamId: "123",
+ },
+ query: {},
+ body: {},
+ db: {
+ getMonitorsByTeamId: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if param validation fails", async () => {
+ req.params = {};
+ await getMonitorsByTeamId(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if query validation fails", async () => {
+ req.query = { invalid: 1 };
+ await getMonitorsByTeamId(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if DB operations fail", async () => {
+ req.db.getMonitorsByTeamId.throws(new Error("DB error"));
+ await getMonitorsByTeamId(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should return success message and data if all operations succeed", async () => {
+ const data = { monitors: "data" };
+ req.db.getMonitorsByTeamId.returns(data);
+ await getMonitorsByTeamId(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.teamId),
+ data: data,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - createMonitor", () => {
+ beforeEach(() => {
+ req = {
+ params: {},
+ query: {},
+ body: {
+ userId: "123",
+ teamId: "123",
+ name: "test_monitor",
+ description: "test_monitor_desc",
+ type: "http",
+ url: "https://example.com",
+ notifications: [{ email: "example@example.com" }],
+ },
+ db: {
+ createMonitor: sinon.stub(),
+ createNotification: sinon.stub(),
+ },
+ jobQueue: {
+ addJob: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if body validation fails", async () => {
+ req.body = {};
+ await createMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if DB createMonitor operation fail", async () => {
+ req.db.createMonitor.throws(new Error("DB error"));
+ await createMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should reject with an error if DB createNotification operation fail", async () => {
+ req.db.createNotification.throws(new Error("DB error"));
+ req.db.createMonitor.returns({ _id: "123" });
+ await createMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should reject with an error if monitor.save operation fail", async () => {
+ req.db.createMonitor.returns({
+ _id: "123",
+ save: sinon.stub().throws(new Error("Monitor save error")),
+ });
+ await createMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Monitor save error");
+ });
+ it("should throw an error if addJob operation fails", async () => {
+ req.db.createMonitor.returns({ _id: "123", save: sinon.stub() });
+ req.jobQueue.addJob.throws(new Error("Job error"));
+ await createMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Job error");
+ });
+ it("should return success message and data if all operations succeed with empty notifications", async () => {
+ req.body.notifications = [];
+ const monitor = { _id: "123", save: sinon.stub() };
+ req.db.createMonitor.returns(monitor);
+ await createMonitor(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(201);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_CREATE,
+ data: monitor,
+ })
+ ).to.be.true;
+ });
+ it("should return success message and data if all operations succeed", async () => {
+ const monitor = { _id: "123", save: sinon.stub() };
+ req.db.createMonitor.returns(monitor);
+ await createMonitor(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(201);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_CREATE,
+ data: monitor,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - deleteMonitor", () => {
+ beforeEach(() => {
+ req = {
+ params: {
+ monitorId: "123",
+ },
+ query: {},
+ body: {},
+ db: {
+ deleteMonitor: sinon.stub(),
+ deleteChecks: sinon.stub(),
+ deletePageSpeedChecksByMonitorId: sinon.stub(),
+ deleteNotificationsByMonitorId: sinon.stub(),
+ },
+ jobQueue: {
+ deleteJob: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ sinon.stub(logger, "error");
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if param validation fails", async () => {
+ req.params = {};
+ await deleteMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if DB deleteMonitor operation fail", async () => {
+ req.db.deleteMonitor.throws(new Error("DB error"));
+ await deleteMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should log an error if deleteJob throws an error", async () => {
+ const error = new Error("Job error");
+ const monitor = { name: "test_monitor", _id: "123" };
+ req.db.deleteMonitor.returns(monitor);
+ req.jobQueue.deleteJob.rejects(error);
+ await monitorController.deleteMonitor(req, res, next);
+ expect(logger.error.calledOnce).to.be.true;
+ expect(logger.error.firstCall.args[0]).to.equal(
+ `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
+ );
+ expect(logger.error.firstCall.args[1]).to.deep.equal({
+ method: "deleteMonitor",
+ service: SERVICE_NAME,
+ error: error.message,
+ });
+ });
+ it("should log an error if deleteChecks throws an error", async () => {
+ const error = new Error("Checks error");
+ const monitor = { name: "test_monitor", _id: "123" };
+ req.db.deleteMonitor.returns(monitor);
+ req.db.deleteChecks.rejects(error);
+ await monitorController.deleteMonitor(req, res, next);
+ expect(logger.error.calledOnce).to.be.true;
+ expect(logger.error.firstCall.args[0]).to.equal(
+ `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
+ );
+ expect(logger.error.firstCall.args[1]).to.deep.equal({
+ method: "deleteMonitor",
+ service: SERVICE_NAME,
+ error: error.message,
+ });
+ });
+ it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async () => {
+ const error = new Error("PageSpeed error");
+ const monitor = { name: "test_monitor", _id: "123" };
+ req.db.deleteMonitor.returns(monitor);
+ req.db.deletePageSpeedChecksByMonitorId.rejects(error);
+ await monitorController.deleteMonitor(req, res, next);
+ expect(logger.error.calledOnce).to.be.true;
+ expect(logger.error.firstCall.args[0]).to.equal(
+ `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
+ );
+ expect(logger.error.firstCall.args[1]).to.deep.equal({
+ method: "deleteMonitor",
+ service: SERVICE_NAME,
+ error: error.message,
+ });
+ });
+ it("should log an error if deleteNotificationsByMonitorId throws an error", async () => {
+ const error = new Error("Notifications error");
+ const monitor = { name: "test_monitor", _id: "123" };
+ req.db.deleteMonitor.returns(monitor);
+ req.db.deleteNotificationsByMonitorId.rejects(error);
+ await monitorController.deleteMonitor(req, res, next);
+ expect(logger.error.calledOnce).to.be.true;
+ expect(logger.error.firstCall.args[0]).to.equal(
+ `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
+ );
+ expect(logger.error.firstCall.args[1]).to.deep.equal({
+ method: "deleteMonitor",
+ service: SERVICE_NAME,
+ error: error.message,
+ });
+ });
+ it("should return success message if all operations succeed", async () => {
+ const monitor = { name: "test_monitor", _id: "123" };
+ req.db.deleteMonitor.returns(monitor);
+ await monitorController.deleteMonitor(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_DELETE,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - deleteAllMonitors", () => {
+ beforeEach(() => {
+ stub = sinon.stub(jwt, "verify").callsFake(() => {
+ return { teamId: "123" };
+ });
+ req = {
+ headers: {
+ authorization: "Bearer token",
+ },
+ params: {
+ monitorId: "123",
+ },
+ query: {},
+ body: {},
+ db: {
+ deleteAllMonitors: sinon.stub(),
+ deleteChecks: sinon.stub(),
+ deletePageSpeedChecksByMonitorId: sinon.stub(),
+ deleteNotificationsByMonitorId: sinon.stub(),
+ },
+ jobQueue: {
+ deleteJob: sinon.stub(),
+ },
+ settingsService: {
+ getSettings: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ sinon.stub(logger, "error");
+ });
+ afterEach(() => {
+ sinon.restore();
+ stub.restore();
+ });
+ it("should reject with an error if getTokenFromHeaders throws an error", async () => {
+ req.headers = {};
+ await deleteAllMonitors(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("No auth headers");
+ expect(next.firstCall.args[0].status).to.equal(500);
+ });
+ it("should reject with an error if token validation fails", async () => {
+ stub.restore();
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ await deleteAllMonitors(req, res, next);
+ expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
+ });
+ it("should reject with an error if DB deleteAllMonitors operation fail", async () => {
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.db.deleteAllMonitors.throws(new Error("DB error"));
+ await deleteAllMonitors(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should log an error if deleteJob throws an error", async () => {
+ const monitors = [{ name: "test_monitor", _id: "123" }];
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
+ const error = new Error("Job error");
+ req.jobQueue.deleteJob.rejects(error);
+ await deleteAllMonitors(req, res, next);
+ expect(logger.error.calledOnce).to.be.true;
+ expect(logger.error.firstCall.args[0]).to.deep.equal(
+ `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
+ );
+ });
+ it("should log an error if deleteChecks throws an error", async () => {
+ const monitors = [{ name: "test_monitor", _id: "123" }];
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
+ const error = new Error("Check error");
+ req.db.deleteChecks.rejects(error);
+ await deleteAllMonitors(req, res, next);
+ expect(logger.error.calledOnce).to.be.true;
+ expect(logger.error.firstCall.args[0]).to.deep.equal(
+ `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
+ );
+ });
+ it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async () => {
+ const monitors = [{ name: "test_monitor", _id: "123" }];
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
+ const error = new Error("Pagespeed Check error");
+ req.db.deletePageSpeedChecksByMonitorId.rejects(error);
+ await deleteAllMonitors(req, res, next);
+ expect(logger.error.calledOnce).to.be.true;
+ expect(logger.error.firstCall.args[0]).to.deep.equal(
+ `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
+ );
+ });
+ it("should log an error if deleteNotificationsByMonitorId throws an error", async () => {
+ const monitors = [{ name: "test_monitor", _id: "123" }];
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
+ const error = new Error("Notifications Check error");
+ req.db.deleteNotificationsByMonitorId.rejects(error);
+ await deleteAllMonitors(req, res, next);
+ expect(logger.error.calledOnce).to.be.true;
+ expect(logger.error.firstCall.args[0]).to.deep.equal(
+ `Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
+ );
+ });
+});
+
+describe("Monitor Controller - editMonitor", () => {
+ beforeEach(() => {
+ req = {
+ headers: {},
+ params: {
+ monitorId: "123",
+ },
+ query: {},
+ body: {
+ notifications: [{ email: "example@example.com" }],
+ },
+ db: {
+ getMonitorById: sinon.stub(),
+ editMonitor: sinon.stub(),
+ deleteNotificationsByMonitorId: sinon.stub(),
+ createNotification: sinon.stub(),
+ },
+ jobQueue: {
+ deleteJob: sinon.stub(),
+ addJob: sinon.stub(),
+ },
+ settingsService: {
+ getSettings: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if param validation fails", async () => {
+ req.params = {};
+ await editMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if body validation fails", async () => {
+ req.body = { invalid: 1 };
+ await editMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if getMonitorById operation fails", async () => {
+ req.db.getMonitorById.throws(new Error("DB error"));
+ await editMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should reject with an error if editMonitor operation fails", async () => {
+ req.db.getMonitorById.returns({ teamId: "123" });
+ req.db.editMonitor.throws(new Error("DB error"));
+ await editMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should reject with an error if deleteNotificationsByMonitorId operation fails", async () => {
+ req.db.getMonitorById.returns({ teamId: "123" });
+ req.db.editMonitor.returns({ _id: "123" });
+ req.db.deleteNotificationsByMonitorId.throws(new Error("DB error"));
+ await editMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should reject with an error if createNotification operation fails", async () => {
+ req.db.getMonitorById.returns({ teamId: "123" });
+ req.db.editMonitor.returns({ _id: "123" });
+ req.db.createNotification.throws(new Error("DB error"));
+ await editMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should reject with an error if deleteJob operation fails", async () => {
+ req.db.getMonitorById.returns({ teamId: "123" });
+ req.db.editMonitor.returns({ _id: "123" });
+ req.jobQueue.deleteJob.throws(new Error("Job error"));
+ await editMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Job error");
+ });
+ it("should reject with an error if addJob operation fails", async () => {
+ req.db.getMonitorById.returns({ teamId: "123" });
+ req.db.editMonitor.returns({ _id: "123" });
+ req.jobQueue.addJob.throws(new Error("Add Job error"));
+ await editMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Add Job error");
+ });
+ it("should return success message with data if all operations succeed and empty notifications", async () => {
+ req.body.notifications = [];
+ const monitor = { _id: "123" };
+ req.db.getMonitorById.returns({ teamId: "123" });
+ req.db.editMonitor.returns(monitor);
+ await editMonitor(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_EDIT,
+ data: monitor,
+ })
+ ).to.be.true;
+ });
+ it("should return success message with data if all operations succeed", async () => {
+ const monitor = { _id: "123" };
+ req.db.getMonitorById.returns({ teamId: "123" });
+ req.db.editMonitor.returns(monitor);
+ await editMonitor(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_EDIT,
+ data: monitor,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - pauseMonitor", () => {
+ beforeEach(() => {
+ req = {
+ headers: {},
+ params: {
+ monitorId: "123",
+ },
+ query: {},
+ body: {},
+ db: {
+ getMonitorById: sinon.stub(),
+ },
+ jobQueue: {
+ deleteJob: sinon.stub(),
+ addJob: sinon.stub(),
+ },
+ settingsService: {
+ getSettings: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it("should reject with an error if param validation fails", async () => {
+ req.params = {};
+ await pauseMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].status).to.equal(422);
+ });
+ it("should reject with an error if getMonitorById operation fails", async () => {
+ req.db.getMonitorById.throws(new Error("DB error"));
+ await pauseMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should reject with an error if deleteJob operation fails", async () => {
+ const monitor = { _id: req.params.monitorId, isActive: true };
+ req.db.getMonitorById.returns(monitor);
+ req.jobQueue.deleteJob.throws(new Error("Delete Job error"));
+ await pauseMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Delete Job error");
+ });
+
+ it("should reject with an error if addJob operation fails", async () => {
+ const monitor = { _id: req.params.monitorId, isActive: false };
+ req.db.getMonitorById.returns(monitor);
+ req.jobQueue.addJob.throws(new Error("Add Job error"));
+ await pauseMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Add Job error");
+ });
+ it("should reject with an error if monitor.save operation fails", async () => {
+ const monitor = {
+ _id: req.params.monitorId,
+ active: false,
+ save: sinon.stub().throws(new Error("Save error")),
+ };
+ req.db.getMonitorById.returns(monitor);
+ await pauseMonitor(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Save error");
+ });
+ it("should return success pause message with data if all operations succeed with inactive monitor", async () => {
+ const monitor = {
+ _id: req.params.monitorId,
+ isActive: false,
+ save: sinon.stub().resolves(),
+ };
+ req.db.getMonitorById.returns(monitor);
+ await pauseMonitor(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_PAUSE,
+ data: monitor,
+ })
+ ).to.be.true;
+ });
+ it("should return success resume message with data if all operations succeed with active monitor", async () => {
+ const monitor = {
+ _id: req.params.monitorId,
+ isActive: true,
+ save: sinon.stub().resolves(),
+ };
+ req.db.getMonitorById.returns(monitor);
+ await pauseMonitor(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_RESUME,
+ data: monitor,
+ })
+ ).to.be.true;
+ });
+});
+
+describe("Monitor Controller - addDemoMonitors", () => {
+ beforeEach(() => {
+ stub = sinon.stub(jwt, "verify").callsFake(() => {
+ return { _id: "123", teamId: "123" };
+ });
+ req = {
+ headers: {
+ authorization: "Bearer token",
+ },
+ params: {},
+ query: {},
+ body: {},
+ db: {
+ addDemoMonitors: sinon.stub(),
+ },
+ settingsService: {
+ getSettings: sinon.stub(),
+ },
+ jobQueue: {
+ addJob: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ stub.restore();
+ });
+ it("should reject with an error if getTokenFromHeaders fails", async () => {
+ req.headers = {};
+ await addDemoMonitors(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("No auth headers");
+ expect(next.firstCall.args[0].status).to.equal(500);
+ });
+ it("should reject with an error if getting settings fails", async () => {
+ req.settingsService.getSettings.throws(new Error("Settings error"));
+ await addDemoMonitors(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Settings error");
+ });
+ it("should reject with an error if JWT validation fails", async () => {
+ stub.restore();
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ await addDemoMonitors(req, res, next);
+ expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError);
+ });
+ it("should reject with an error if addDemoMonitors operation fails", async () => {
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.db.addDemoMonitors.throws(new Error("DB error"));
+ await addDemoMonitors(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("DB error");
+ });
+ it("should reject with an error if addJob operation fails", async () => {
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.db.addDemoMonitors.returns([{ _id: "123" }]);
+ req.jobQueue.addJob.throws(new Error("Add Job error"));
+ await addDemoMonitors(req, res, next);
+ expect(next.firstCall.args[0]).to.be.an("error");
+ expect(next.firstCall.args[0].message).to.equal("Add Job error");
+ });
+ it("should return success message with data if all operations succeed", async () => {
+ const monitors = [{ _id: "123" }];
+ req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
+ req.db.addDemoMonitors.returns(monitors);
+ await addDemoMonitors(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(
+ res.json.calledOnceWith({
+ success: true,
+ msg: successMessages.MONITOR_DEMO_ADDED,
+ data: monitors.length,
+ })
+ ).to.be.true;
+ });
+});
diff --git a/Server/tests/controllers/queueController.test.js b/Server/tests/controllers/queueController.test.js
new file mode 100644
index 000000000..476905b54
--- /dev/null
+++ b/Server/tests/controllers/queueController.test.js
@@ -0,0 +1,168 @@
+const { afterEach } = require("node:test");
+const {
+ getMetrics,
+ getJobs,
+ addJob,
+ obliterateQueue,
+} = require("../../controllers/queueController");
+const SERVICE_NAME = "JobQueueController";
+
+const { errorMessages, successMessages } = require("../../utils/messages");
+const sinon = require("sinon");
+
+describe("Queue Controller - getMetrics", () => {
+ beforeEach(() => {
+ req = {
+ headers: {},
+ params: {},
+ body: {},
+ db: {},
+ jobQueue: {
+ getMetrics: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ handleError = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should throw an error if getMetrics throws an error", async () => {
+ 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 () => {
+ const data = { data: "metrics" };
+ req.jobQueue.getMetrics.returns(data);
+ await getMetrics(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(res.json.firstCall.args[0]).to.deep.equal({
+ success: true,
+ msg: successMessages.QUEUE_GET_METRICS,
+ data,
+ });
+ });
+});
+
+describe("Queue Controller - getJobs", () => {
+ beforeEach(() => {
+ req = {
+ headers: {},
+ params: {},
+ body: {},
+ db: {},
+ jobQueue: {
+ getJobStats: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ handleError = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if getJobs throws an error", async () => {
+ 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 () => {
+ const data = { data: "jobs" };
+ req.jobQueue.getJobStats.returns(data);
+ await getJobs(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(res.json.firstCall.args[0]).to.deep.equal({
+ success: true,
+ msg: successMessages.QUEUE_GET_METRICS,
+ data,
+ });
+ });
+});
+
+describe("Queue Controller - addJob", () => {
+ beforeEach(() => {
+ req = {
+ headers: {},
+ params: {},
+ body: {},
+ db: {},
+ jobQueue: {
+ addJob: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ handleError = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if addJob throws an error", async () => {
+ 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 () => {
+ req.jobQueue.addJob.resolves();
+ await addJob(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(res.json.firstCall.args[0]).to.deep.equal({
+ success: true,
+ msg: successMessages.QUEUE_ADD_JOB,
+ });
+ });
+});
+
+describe("Queue Controller - obliterateQueue", () => {
+ beforeEach(() => {
+ req = {
+ headers: {},
+ params: {},
+ body: {},
+ db: {},
+ jobQueue: {
+ obliterate: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ handleError = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if obliterateQueue throws an error", async () => {
+ 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 () => {
+ req.jobQueue.obliterate.resolves();
+ await obliterateQueue(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(res.json.firstCall.args[0]).to.deep.equal({
+ success: true,
+ msg: successMessages.QUEUE_OBLITERATE,
+ });
+ });
+});
diff --git a/Server/tests/controllers/settingsController.test.js b/Server/tests/controllers/settingsController.test.js
new file mode 100644
index 000000000..b06565f38
--- /dev/null
+++ b/Server/tests/controllers/settingsController.test.js
@@ -0,0 +1,105 @@
+const { afterEach } = require("node:test");
+const {
+ getAppSettings,
+ updateAppSettings,
+} = require("../../controllers/settingsController");
+
+const { errorMessages, successMessages } = require("../../utils/messages");
+const sinon = require("sinon");
+
+describe("Settings Controller - getAppSettings", () => {
+ beforeEach(() => {
+ req = {
+ headers: {},
+ params: {},
+ body: {},
+ db: {},
+ settingsService: {
+ getSettings: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ handleError = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should throw an error if getSettings throws an error", async () => {
+ 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 () => {
+ const data = { data: "settings" };
+ req.settingsService.getSettings.returns(data);
+ await getAppSettings(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(res.json.firstCall.args[0]).to.deep.equal({
+ success: true,
+ msg: successMessages.GET_APP_SETTINGS,
+ data,
+ });
+ });
+});
+
+describe("Settings Controller - updateAppSettings", () => {
+ beforeEach(() => {
+ req = {
+ headers: {},
+ params: {},
+ body: {},
+ db: {
+ updateAppSettings: sinon.stub(),
+ },
+ settingsService: {
+ reloadSettings: sinon.stub(),
+ },
+ };
+ res = {
+ status: sinon.stub().returnsThis(),
+ json: sinon.stub(),
+ };
+ next = sinon.stub();
+ handleError = sinon.stub();
+ });
+ afterEach(() => {
+ sinon.restore();
+ });
+ it("should reject with an error if body validation fails", async () => {
+ 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 () => {
+ 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 () => {
+ 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 () => {
+ const data = { data: "settings" };
+ req.settingsService.reloadSettings.returns(data);
+ await updateAppSettings(req, res, next);
+ expect(res.status.firstCall.args[0]).to.equal(200);
+ expect(res.json.firstCall.args[0]).to.deep.equal({
+ success: true,
+ msg: successMessages.UPDATE_APP_SETTINGS,
+ data,
+ });
+ });
+});
diff --git a/Server/utils/messages.js b/Server/utils/messages.js
index 16b2f7883..d685e7326 100644
--- a/Server/utils/messages.js
+++ b/Server/utils/messages.js
@@ -86,6 +86,12 @@ const successMessages = {
MONITOR_CERTIFICATE: "Got monitor certificate successfully",
MONITOR_DEMO_ADDED: "Successfully added demo monitors",
+ // Queue Controller
+ QUEUE_GET_METRICS: "Got metrics successfully",
+ QUEUE_GET_METRICS: "Got job stats successfully",
+ QUEUE_ADD_JOB: "Job added successfully",
+ QUEUE_OBLITERATE: "Queue obliterated",
+
//Job Queue
JOB_QUEUE_DELETE_JOB: "Job removed successfully",
JOB_QUEUE_OBLITERATE: "Queue OBLITERATED!!!",
diff --git a/Server/validation/joi.js b/Server/validation/joi.js
index 3133da80a..9e36c2135 100644
--- a/Server/validation/joi.js
+++ b/Server/validation/joi.js
@@ -10,8 +10,8 @@ const { start } = require("repl");
const roleValidatior = (role) => (value, helpers) => {
const hasRole = role.some((role) => value.includes(role));
if (!hasRole) {
- throw new Joi.ValidationError(
- `You do not have the required authorization. Required roles: ${roles.join(", ")}`
+ throw new joi.ValidationError(
+ `You do not have the required authorization. Required roles: ${role.join(", ")}`
);
}
return value;