diff --git a/Server/.mocharc.cjs b/Server/.mocharc.cjs index fffc60e2f..e4c8f0dd7 100644 --- a/Server/.mocharc.cjs +++ b/Server/.mocharc.cjs @@ -1,8 +1,8 @@ module.exports = { - require: ["esm", "chai/register-expect.js"], // Include Chai's "expect" interface globally - spec: "tests/**/*.test.js", // Specify test files - timeout: 5000, // Set test-case timeout in milliseconds - recursive: true, // Include subdirectories - reporter: "spec", // Use the "spec" reporter - exit: true, // Force Mocha to quit after tests complete + require: ["esm", "chai/register-expect.js"], // Include Chai's "expect" interface globally + spec: "tests/**/*.test.js", // Specify test files + timeout: 5000, // Set test-case timeout in milliseconds + recursive: true, // Include subdirectories + reporter: "spec", // Use the "spec" reporter + exit: true, // Force Mocha to quit after tests complete }; diff --git a/Server/.mocharc.js b/Server/.mocharc.js deleted file mode 100644 index 5c8840544..000000000 --- a/Server/.mocharc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - require: ["chai/register-expect.js"], // Include Chai's "expect" interface globally - spec: "tests/**/*.test.js", // Specify test files - timeout: 5000, // Set test-case timeout in milliseconds - recursive: true, // Include subdirectories - reporter: "spec", // Use the "spec" reporter - exit: true, // Force Mocha to quit after tests complete -}; diff --git a/Server/controllers/controllerUtils.js b/Server/controllers/controllerUtils.js index f349fab08..313c2a09a 100644 --- a/Server/controllers/controllerUtils.js +++ b/Server/controllers/controllerUtils.js @@ -1,6 +1,3 @@ -import sslChecker from "ssl-checker"; -import * as tls from "tls"; - const handleValidationError = (error, serviceName) => { error.status = 422; error.service = serviceName; @@ -15,11 +12,11 @@ const handleError = (error, serviceName, method, status = 500) => { return error; }; -const fetchMonitorCertificate = async (monitor) => { +const fetchMonitorCertificate = async (tls, monitor) => { return new Promise((resolve, reject) => { + const monitorUrl = new URL(monitor.url); + const hostname = monitorUrl.hostname; try { - const monitorUrl = new URL(monitor.url); - const hostname = monitorUrl.hostname; let socket = tls.connect( { port: 443, @@ -37,34 +34,10 @@ const fetchMonitorCertificate = async (monitor) => { } } ); - socket.on("error", (error) => { - console.log(error); - reject( - new Error( - `TLS connection error while fetching certificate from ${error.hostname}` - ) - ); - }); } catch (error) { reject(error); } }); }; -const test = async () => { - let host = "httpbin.org"; - console.log(cert.validTo); - let socket = tls.connect( - { - port: 443, - host, - servername: host, // this is required in case the server enabled SNI - }, - () => { - let x509Certificate = socket.getPeerX509Certificate(); - console.log(x509Certificate.validTo); - } - ); -}; - -export { handleValidationError, handleError, fetchMonitorCertificate, test }; +export { handleValidationError, handleError, fetchMonitorCertificate }; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 665cfbaee..3c43f7d31 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -12,6 +12,7 @@ import { getMonitorStatsByIdQueryValidation, getCertificateParamValidation, } from "../validation/joi.js"; +import * as tls from "tls"; const SERVICE_NAME = "monitorController"; import { errorMessages, successMessages } from "../utils/messages.js"; @@ -86,7 +87,7 @@ const getMonitorCertificate = async (req, res, next) => { try { const { monitorId } = req.params; const monitor = await req.db.getMonitorById(monitorId); - const certificate = await fetchMonitorCertificate(monitor); + const certificate = await fetchMonitorCertificate(tls, monitor); if (certificate && certificate.validTo) { return res.status(200).json({ success: true, diff --git a/Server/tests/controllers/controllerUtils.test.js b/Server/tests/controllers/controllerUtils.test.js index 93cc4d023..353827084 100644 --- a/Server/tests/controllers/controllerUtils.test.js +++ b/Server/tests/controllers/controllerUtils.test.js @@ -1,105 +1,210 @@ +import sinon from "sinon"; + import { - handleValidationError, - handleError, + handleValidationError, + handleError, + fetchMonitorCertificate, } from "../../controllers/controllerUtils.js"; +import { expect } from "chai"; 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 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 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.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 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"); - }); + 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); - }); +describe("controllerUtils - 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 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 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 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 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 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); - }); + 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); + }); +}); + +describe("controllerUtils - fetchMonitorCertificate", () => { + const originalTls = { + connect: sinon.stub().callsFake((options, callback) => { + // Create socket stub with sinon stubs for all methods + socket = { + getPeerX509Certificate: sinon.stub().returns({ + subject: "CN=fake-cert", + validTo: "Dec 31 23:59:59 2023 GMT", + }), + end: sinon.stub(), + on: sinon.stub(), + }; + + // Use process.nextTick to ensure async behavior + process.nextTick(() => { + callback.call(socket); // Ensure correct 'this' binding + }); + + return socket; + }), + }; + + let tls, monitor, socket; + beforeEach(() => { + monitor = { url: "https://www.google.com" }; + tls = { + connect: sinon.stub().callsFake((options, callback) => { + // Create socket stub with sinon stubs for all methods + socket = { + getPeerX509Certificate: sinon.stub().returns({ + subject: "CN=fake-cert", + validTo: "Dec 31 23:59:59 2023 GMT", + }), + end: sinon.stub(), + on: sinon.stub(), + }; + + // Use process.nextTick to ensure async behavior + process.nextTick(() => { + callback.call(socket); // Ensure correct 'this' binding + }); + + return socket; + }), + }; + }); + + afterEach(() => { + tls = { ...originalTls }; + sinon.restore(); + }); + + it("should resolve with the certificate when the connection is successful", async () => { + const certificate = await fetchMonitorCertificate(tls, monitor); + expect(certificate.validTo).to.equal("Dec 31 23:59:59 2023 GMT"); + expect(socket.end.calledOnce).to.be.true; + }); + + it("should reject with an error when the connection fails", async () => { + tls.connect = sinon.stub().throws(new Error("Connection error")); + try { + await fetchMonitorCertificate(tls, monitor); + } catch (error) { + expect(error.message).to.equal("Connection error"); + } + }); + + it("should reject with an error if monitorURL is invalid", async () => { + monitor.url = "invalid-url"; + try { + await fetchMonitorCertificate(tls, monitor); + } catch (error) { + expect(error.message).to.equal("Invalid URL"); + } + }); + it("should do a thing", async () => { + tls = { + connect: sinon.stub().callsFake((options, callback) => { + // Create socket stub with sinon stubs for all methods + socket = { + getPeerX509Certificate: sinon.stub().throws(new Error("Certificate error")), + end: sinon.stub(), + on: sinon.stub(), + }; + + // Use process.nextTick to ensure async behavior + process.nextTick(() => { + callback.call(socket); // Ensure correct 'this' binding + }); + + return socket; + }), + }; + try { + await fetchMonitorCertificate(tls, monitor); + } catch (error) { + console.log(error); + } + }); });