From 57cad8d31a9183b4434d7b4c2ed771555107c978 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 12 Nov 2024 16:16:20 +0800 Subject: [PATCH 1/4] Inject generateAvatarImage into insertUser --- Server/db/mongo/modules/userModule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js index 69aa52b60..7737d848e 100644 --- a/Server/db/mongo/modules/userModule.js +++ b/Server/db/mongo/modules/userModule.js @@ -15,7 +15,7 @@ const SERVICE_NAME = "userModule"; * @returns {Promise} * @throws {Error} */ -const insertUser = async (userData, imageFile) => { +const insertUser = async (userData, imageFile, GenerateAvatarImage) => { try { if (imageFile) { // 1. Save the full size image From d9812a446b7daf94c444dcb47669592ab37987c9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 12 Nov 2024 16:42:49 +0800 Subject: [PATCH 2/4] update function dependency injection --- Server/db/mongo/modules/userModule.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js index 7737d848e..f86c6e28b 100644 --- a/Server/db/mongo/modules/userModule.js +++ b/Server/db/mongo/modules/userModule.js @@ -15,7 +15,11 @@ const SERVICE_NAME = "userModule"; * @returns {Promise} * @throws {Error} */ -const insertUser = async (userData, imageFile, GenerateAvatarImage) => { +const insertUser = async ( + userData, + imageFile, + generateAvatarImage = GenerateAvatarImage +) => { try { if (imageFile) { // 1. Save the full size image @@ -25,7 +29,7 @@ const insertUser = async (userData, imageFile, GenerateAvatarImage) => { }; // 2. Get the avatar sized image - const avatar = await GenerateAvatarImage(imageFile); + const avatar = await generateAvatarImage(imageFile); userData.avatarImage = avatar; } @@ -90,7 +94,12 @@ const getUserByEmail = async (email) => { * @throws {Error} */ -const updateUser = async (req, res) => { +const updateUser = async ( + req, + res, + parseBoolean = ParseBoolean, + generateAvatarImage = GenerateAvatarImage +) => { const candidateUserId = req.params.userId; try { const candidateUser = { ...req.body }; @@ -98,7 +107,7 @@ const updateUser = async (req, res) => { // Handle profile image // ****************************************** - if (ParseBoolean(candidateUser.deleteProfileImage) === true) { + if (parseBoolean(candidateUser.deleteProfileImage) === true) { candidateUser.profileImage = null; candidateUser.avatarImage = null; } else if (req.file) { @@ -108,8 +117,8 @@ const updateUser = async (req, res) => { contentType: req.file.mimetype, }; - // 2. Get the avaatar sized image - const avatar = await GenerateAvatarImage(req.file); + // 2. Get the avatar sized image + const avatar = await generateAvatarImage(req.file); candidateUser.avatarImage = avatar; } @@ -164,6 +173,7 @@ const deleteUser = async (userId) => { const deleteTeam = async (teamId) => { try { await TeamModel.findByIdAndDelete(teamId); + return true; } catch (error) { error.service = SERVICE_NAME; error.method = "deleteTeam"; From 4c7c4d35782be511a55a84b63d458b3a00ab33f2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 12 Nov 2024 16:44:01 +0800 Subject: [PATCH 3/4] Add return type for deleteAllOtherUsers --- Server/db/mongo/modules/userModule.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js index f86c6e28b..5b951ff64 100644 --- a/Server/db/mongo/modules/userModule.js +++ b/Server/db/mongo/modules/userModule.js @@ -184,6 +184,7 @@ const deleteTeam = async (teamId) => { const deleteAllOtherUsers = async () => { try { await UserModel.deleteMany({ role: { $ne: "superadmin" } }); + return true; } catch (error) { error.service = SERVICE_NAME; error.method = "deleteAllOtherUsers"; From 1733918f0871e1e4185df5ba8dd18881742c6921 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 12 Nov 2024 16:48:20 +0800 Subject: [PATCH 4/4] Add tests for user module --- Server/tests/db/userModule.test.js | 286 +++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 Server/tests/db/userModule.test.js diff --git a/Server/tests/db/userModule.test.js b/Server/tests/db/userModule.test.js new file mode 100644 index 000000000..8f9fd4dc2 --- /dev/null +++ b/Server/tests/db/userModule.test.js @@ -0,0 +1,286 @@ +import sinon from "sinon"; +import UserModel from "../../db/models/User.js"; +import TeamModel from "../../db/models/Team.js"; +import { + insertUser, + getUserByEmail, + updateUser, + deleteUser, + deleteTeam, + deleteAllOtherUsers, + getAllUsers, + logoutUser, +} from "../../db/mongo/modules/userModule.js"; +import { errorMessages } from "../../utils/messages.js"; + +const mockUser = { + email: "test@test.com", + password: "password", + role: ["user"], +}; +const mockSuperUser = { + email: "test@test.com", + password: "password", + role: ["superadmin"], +}; +const imageFile = { + image: 1, +}; + +describe("userModule", () => { + let teamSaveStub, + teamFindByIdAndDeleteStub, + userSaveStub, + userFindStub, + userFindOneStub, + userFindByIdAndUpdateStub, + userFindByIdAndDeleteStub, + userDeleteManyStub, + userUpdateOneStub, + generateAvatarImageStub, + parseBooleanStub; + beforeEach(() => { + teamSaveStub = sinon.stub(TeamModel.prototype, "save"); + teamFindByIdAndDeleteStub = sinon.stub(TeamModel, "findByIdAndDelete"); + userSaveStub = sinon.stub(UserModel.prototype, "save"); + userFindStub = sinon.stub(UserModel, "find"); + userFindOneStub = sinon.stub(UserModel, "findOne"); + userFindByIdAndUpdateStub = sinon.stub(UserModel, "findByIdAndUpdate"); + userFindByIdAndDeleteStub = sinon.stub(UserModel, "findByIdAndDelete"); + userDeleteManyStub = sinon.stub(UserModel, "deleteMany"); + userUpdateOneStub = sinon.stub(UserModel, "updateOne"); + generateAvatarImageStub = sinon.stub().resolves({ image: 2 }); + parseBooleanStub = sinon.stub().returns(true); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe("insertUser", () => { + it("should insert a regular user", async () => { + userSaveStub.resolves(mockUser); + userFindOneStub.returns({ + select: sinon.stub().returns({ + select: sinon.stub().resolves(mockUser), + }), + }); + const result = await insertUser(mockUser, imageFile, generateAvatarImageStub); + expect(result).to.deep.equal(mockUser); + }); + it("should insert a superadmin user", async () => { + userSaveStub.resolves(mockSuperUser); + userFindOneStub.returns({ + select: sinon.stub().returns({ + select: sinon.stub().resolves(mockSuperUser), + }), + }); + const result = await insertUser(mockSuperUser, imageFile, generateAvatarImageStub); + expect(result).to.deep.equal(mockSuperUser); + }); + it("should handle an error", async () => { + const err = new Error("test error"); + userSaveStub.rejects(err); + try { + await insertUser(mockUser, imageFile, generateAvatarImageStub); + } catch (error) { + expect(error).to.exist; + expect(error).to.deep.equal(err); + } + }); + it("should handle a duplicate key error", async () => { + const err = new Error("test error"); + err.code = 11000; + userSaveStub.rejects(err); + try { + await insertUser(mockUser, imageFile, generateAvatarImageStub); + } catch (error) { + expect(error).to.exist; + expect(error).to.deep.equal(err); + } + }); + }); + describe("getUserByEmail", () => { + it("should return a user", async () => { + userFindOneStub.returns({ + select: sinon.stub().resolves(mockUser), + }); + const result = await getUserByEmail(mockUser.email); + expect(result).to.deep.equal(mockUser); + }); + }); + describe("getUserByEmail", () => { + it("throw an error if a user is not found", async () => { + userFindOneStub.returns({ + select: sinon.stub().resolves(null), + }); + try { + await getUserByEmail(mockUser.email); + } catch (error) { + expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); + } + }); + }); + describe("updateUser", () => { + let req, res; + beforeEach(() => { + req = { + params: { + userId: "testId", + }, + body: { + deleteProfileImage: "false", + email: "test@test.com", + }, + file: { + buffer: "test", + mimetype: "test", + }, + }; + res = {}; + }); + + afterEach(() => {}); + it("should update a user", async () => { + parseBooleanStub.returns(false); + userFindByIdAndUpdateStub.returns({ + select: sinon.stub().returns({ + select: sinon.stub().resolves(mockUser), + }), + }); + const result = await updateUser( + req, + res, + parseBooleanStub, + generateAvatarImageStub + ); + expect(result).to.deep.equal(mockUser); + }); + it("should delete a user profile image", async () => { + req.body.deleteProfileImage = "true"; + userFindByIdAndUpdateStub.returns({ + select: sinon.stub().returns({ + select: sinon.stub().resolves(mockUser), + }), + }); + const result = await updateUser( + req, + res, + parseBooleanStub, + generateAvatarImageStub + ); + expect(result).to.deep.equal(mockUser); + }); + it("should handle an error", async () => { + const err = new Error("test error"); + userFindByIdAndUpdateStub.throws(err); + try { + await updateUser(req, res, parseBooleanStub, generateAvatarImageStub); + } catch (error) { + expect(error).to.exist; + expect(error).to.deep.equal(err); + } + }); + }); + describe("deleteUser", async () => { + it("should return a deleted user", async () => { + userFindByIdAndDeleteStub.resolves(mockUser); + const result = await deleteUser("testId"); + expect(result).to.deep.equal(mockUser); + }); + it("should throw an error if a user is not found", async () => { + try { + await deleteUser("testId"); + } catch (error) { + expect(error).to.exist; + expect(error.message).to.equal(errorMessages.DB_USER_NOT_FOUND); + } + }); + it("should handle an error", async () => { + const err = new Error("test error"); + userFindByIdAndDeleteStub.throws(err); + try { + await deleteUser("testId"); + } catch (error) { + expect(error).to.exist; + expect(error).to.deep.equal(err); + } + }); + }); + + describe("deleteTeam", () => { + it("should return true if team deleted", async () => { + teamFindByIdAndDeleteStub.resolves(); + const result = await deleteTeam("testId"); + expect(result).to.equal(true); + }); + it("should handle an error", async () => { + const err = new Error("test error"); + teamFindByIdAndDeleteStub.throws(err); + try { + await deleteTeam("testId"); + } catch (error) { + expect(error).to.exist; + expect(error).to.deep.equal(err); + } + }); + }); + + describe("deleteAllOtherUsers", () => { + it("should return true if all other users deleted", async () => { + userDeleteManyStub.resolves(true); + const result = await deleteAllOtherUsers(); + expect(result).to.equal(true); + }); + it("should handle an error", async () => { + const err = new Error("test error"); + userDeleteManyStub.throws(err); + try { + await deleteAllOtherUsers(); + } catch (error) { + expect(error).to.exist; + expect(error).to.deep.equal(err); + } + }); + }); + + describe("getAllUsers", () => { + it("should return all users", async () => { + userFindStub.returns({ + select: sinon.stub().returns({ + select: sinon.stub().resolves([mockUser]), + }), + }); + const result = await getAllUsers(); + expect(result).to.deep.equal([mockUser]); + }); + it("should handle an error", async () => { + const err = new Error("test error"); + userFindStub.throws(err); + try { + await getAllUsers(); + } catch (error) { + expect(error).to.exist; + expect(error).to.deep.equal(err); + } + }); + }); + + describe("logoutUser", async () => { + it("should return true if user logged out", async () => { + userUpdateOneStub.resolves(true); + const result = await logoutUser("testId"); + expect(result).to.equal(true); + }); + it("should handle an error", async () => { + const err = new Error("test error"); + userUpdateOneStub.throws(err); + try { + await logoutUser("testId"); + } catch (error) { + expect(error).to.exist; + expect(error).to.deep.equal(err); + } + }); + }); +});