From c0ae2fac77df82690a49d690f063126dab53493b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 17 Oct 2024 13:09:50 +0800 Subject: [PATCH] Add tests for util functions --- Server/.nycrc | 12 ++-- Server/tests/utils/dataUtils.test.js | 68 ++++++++++++++++++++ Server/tests/utils/imageProcessing.test.js | 57 +++++++++++++++++ Server/tests/utils/messages.test.js | 29 +++++++++ Server/tests/utils/utils.test.js | 50 +++++++++++++++ Server/utils/dataUtils.js | 74 ++++++++++------------ 6 files changed, 245 insertions(+), 45 deletions(-) create mode 100644 Server/tests/utils/dataUtils.test.js create mode 100644 Server/tests/utils/imageProcessing.test.js create mode 100644 Server/tests/utils/messages.test.js create mode 100644 Server/tests/utils/utils.test.js diff --git a/Server/.nycrc b/Server/.nycrc index d6406bf7e..d7668b4c9 100644 --- a/Server/.nycrc +++ b/Server/.nycrc @@ -1,8 +1,8 @@ { - "all": true, - "include": ["controllers/*.js"], - "exclude": ["**/*.test.js"], - "reporter": ["html", "text", "lcov"], - "sourceMap": false, - "instrument": true + "all": true, + "include": ["controllers/*.js", "utils/*.js"], + "exclude": ["**/*.test.js"], + "reporter": ["html", "text", "lcov"], + "sourceMap": false, + "instrument": true } diff --git a/Server/tests/utils/dataUtils.test.js b/Server/tests/utils/dataUtils.test.js new file mode 100644 index 000000000..4af193d74 --- /dev/null +++ b/Server/tests/utils/dataUtils.test.js @@ -0,0 +1,68 @@ +import { NormalizeData, calculatePercentile } from "../../utils/dataUtils.js"; +import sinon from "sinon"; + +describe("NormalizeData", () => { + it("should normalize response times when checks length is greater than 1", () => { + const checks = [ + { responseTime: 20, _doc: { id: 1 } }, + { responseTime: 40, _doc: { id: 2 } }, + { responseTime: 60, _doc: { id: 3 } }, + ]; + const rangeMin = 1; + const rangeMax = 100; + + const result = NormalizeData(checks, rangeMin, rangeMax); + + expect(result).to.be.an("array"); + expect(result).to.have.lengthOf(3); + result.forEach((check) => { + expect(check).to.have.property("responseTime").that.is.a("number"); + expect(check).to.have.property("originalResponseTime").that.is.a("number"); + }); + }); + + it("should return checks with original response times when checks length is 1", () => { + const checks = [{ responseTime: 20, _doc: { id: 1 } }]; + const rangeMin = 1; + const rangeMax = 100; + + const result = NormalizeData(checks, rangeMin, rangeMax); + expect(result).to.be.an("array"); + expect(result).to.have.lengthOf(1); + expect(result[0]).to.have.property("originalResponseTime", 20); + }); + + it("should handle edge cases with extreme response times", () => { + const checks = [ + { responseTime: 5, _doc: { id: 1 } }, + { responseTime: 95, _doc: { id: 2 } }, + ]; + const rangeMin = 1; + const rangeMax = 100; + + const result = NormalizeData(checks, rangeMin, rangeMax); + + expect(result).to.be.an("array"); + expect(result).to.have.lengthOf(2); + expect(result[0]).to.have.property("responseTime").that.is.at.least(rangeMin); + expect(result[1]).to.have.property("responseTime").that.is.at.most(rangeMax); + }); +}); + +describe("calculatePercentile", () => { + it("should return the lower value when upper is greater than or equal to the length of the sorted array", () => { + const checks = [ + { responseTime: 10 }, + { responseTime: 20 }, + { responseTime: 30 }, + { responseTime: 40 }, + { responseTime: 50 }, + ]; + + const percentile = 100; + const result = calculatePercentile(checks, percentile); + const expected = 50; + console.log(result); + expect(result).to.equal(expected); + }); +}); diff --git a/Server/tests/utils/imageProcessing.test.js b/Server/tests/utils/imageProcessing.test.js new file mode 100644 index 000000000..dd8ff7049 --- /dev/null +++ b/Server/tests/utils/imageProcessing.test.js @@ -0,0 +1,57 @@ +import { expect } from "chai"; +import sinon from "sinon"; +import sharp from "sharp"; +import { GenerateAvatarImage } from "../../utils/imageProcessing.js"; + +describe("imageProcessing - GenerateAvatarImage", () => { + it("should resize the image to 64x64 and return a base64 string", async () => { + const file = { + buffer: Buffer.from("test image buffer"), + }; + + // Stub the sharp function + const toBufferStub = sinon.stub().resolves(Buffer.from("resized image buffer")); + const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub }); + const sharpStub = sinon + .stub(sharp.prototype, "resize") + .returns({ toBuffer: toBufferStub }); + + const result = await GenerateAvatarImage(file); + + // Verify the result + const expected = Buffer.from("resized image buffer").toString("base64"); + expect(result).to.equal(expected); + + // Verify that the sharp function was called with the correct arguments + expect(sharpStub.calledOnceWith({ width: 64, height: 64, fit: "cover" })).to.be.true; + expect(toBufferStub.calledOnce).to.be.true; + + // Restore the stubbed functions + sharpStub.restore(); + }); + + it("should throw an error if resizing fails", async () => { + const file = { + buffer: Buffer.from("test image buffer"), + }; + + // Stub the sharp function to throw an error + const toBufferStub = sinon.stub().rejects(new Error("Resizing failed")); + const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub }); + const sharpStub = sinon + .stub(sharp.prototype, "resize") + .returns({ toBuffer: toBufferStub }); + + try { + await GenerateAvatarImage(file); + // If no error is thrown, fail the test + expect.fail("Expected error to be thrown"); + } catch (error) { + // Verify that the error message is correct + expect(error.message).to.equal("Resizing failed"); + } + + // Restore the stubbed functions + sharpStub.restore(); + }); +}); diff --git a/Server/tests/utils/messages.test.js b/Server/tests/utils/messages.test.js new file mode 100644 index 000000000..cd8bee371 --- /dev/null +++ b/Server/tests/utils/messages.test.js @@ -0,0 +1,29 @@ +import { errorMessages, successMessages } from "../../utils/messages.js"; +describe("Messages", () => { + describe("messages - errorMessages", () => { + it("should have a DB_FIND_MONITOR_BY_ID function", () => { + const monitorId = "12345"; + expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal( + `Monitor with id ${monitorId} not found` + ); + }); + + it("should have a DB_DELETE_CHECKS function", () => { + const monitorId = "12345"; + expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal( + `No checks found for monitor with id ${monitorId}` + ); + }); + }); + + describe("messages - successMessages", () => { + it("should have a MONITOR_GET_BY_USER_ID function", () => { + const userId = "12345"; + expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal( + `Got monitor for ${userId} successfully"` + ); + }); + + // Add more tests for other success messages as needed + }); +}); diff --git a/Server/tests/utils/utils.test.js b/Server/tests/utils/utils.test.js new file mode 100644 index 000000000..43952c1cb --- /dev/null +++ b/Server/tests/utils/utils.test.js @@ -0,0 +1,50 @@ +import { ParseBoolean, getTokenFromHeaders } from "../../utils/utils.js"; + +describe("utils - ParseBoolean", () => { + it("should return true", () => { + const result = ParseBoolean("true"); + expect(result).to.be.true; + }); + + it("should return false", () => { + const result = ParseBoolean("false"); + expect(result).to.be.false; + }); + + it("should return false", () => { + const result = ParseBoolean(null); + expect(result).to.be.false; + }); + + it("should return false", () => { + const result = ParseBoolean(undefined); + expect(result).to.be.false; + }); +}); + +describe("utils - getTokenFromHeaders", () => { + it("should throw an error if authorization header is missing", () => { + const headers = {}; + expect(() => getTokenFromHeaders(headers)).to.throw("No auth headers"); + }); + + it("should throw an error if authorization header does not start with Bearer", () => { + const headers = { authorization: "Basic abcdef" }; + expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); + }); + + it("should return the token if authorization header is correctly formatted", () => { + const headers = { authorization: "Bearer abcdef" }; + expect(getTokenFromHeaders(headers)).to.equal("abcdef"); + }); + + it("should throw an error if authorization header has more than two parts", () => { + const headers = { authorization: "Bearer abc def" }; + expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); + }); + + it("should throw an error if authorization header has less than two parts", () => { + const headers = { authorization: "Bearer" }; + expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers"); + }); +}); diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index b5e0015d8..b0f8a2fd1 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -1,48 +1,44 @@ const calculatePercentile = (arr, percentile) => { - const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime); - const index = (percentile / 100) * (sorted.length - 1); - const lower = Math.floor(index); - const upper = lower + 1; - const weight = index % 1; - if (upper >= sorted.length) return sorted[lower].responseTime; - return ( - sorted[lower].responseTime * (1 - weight) + - sorted[upper].responseTime * weight - ); + const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime); + const index = (percentile / 100) * (sorted.length - 1); + const lower = Math.floor(index); + const upper = lower + 1; + const weight = index % 1; + if (upper >= sorted.length) return sorted[lower].responseTime; + return sorted[lower].responseTime * (1 - weight) + sorted[upper].responseTime * weight; }; const NormalizeData = (checks, rangeMin, rangeMax) => { - if (checks.length > 1) { - // Get the 5th and 95th percentile - const min = calculatePercentile(checks, 0); - const max = calculatePercentile(checks, 95); + if (checks.length > 1) { + // Get the 5th and 95th percentile + const min = calculatePercentile(checks, 0); + const max = calculatePercentile(checks, 95); - const normalizedChecks = checks.map((check) => { - const originalResponseTime = check.responseTime; - // Normalize the response time between 1 and 100 - let normalizedResponseTime = - rangeMin + - ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min); + const normalizedChecks = checks.map((check) => { + const originalResponseTime = check.responseTime; + // Normalize the response time between 1 and 100 + let normalizedResponseTime = + rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min); - // Put a floor on the response times so we don't have extreme outliers - // Better visuals - normalizedResponseTime = Math.max( - rangeMin, - Math.min(rangeMax, normalizedResponseTime) - ); - return { - ...check._doc, - responseTime: normalizedResponseTime, - originalResponseTime: originalResponseTime, - }; - }); + // Put a floor on the response times so we don't have extreme outliers + // Better visuals + normalizedResponseTime = Math.max( + rangeMin, + Math.min(rangeMax, normalizedResponseTime) + ); + return { + ...check._doc, + responseTime: normalizedResponseTime, + originalResponseTime: originalResponseTime, + }; + }); - return normalizedChecks; - } else { - return checks.map((check) => { - return { ...check._doc, originalResponseTime: check.responseTime }; - }); - } + return normalizedChecks; + } else { + return checks.map((check) => { + return { ...check._doc, originalResponseTime: check.responseTime }; + }); + } }; -export { NormalizeData }; +export { calculatePercentile, NormalizeData };