From 602ee5595df64d7a23bb100542e89b35fb27b992 Mon Sep 17 00:00:00 2001 From: JamesNg Date: Mon, 21 Jul 2025 23:33:04 -0400 Subject: [PATCH 1/6] first commit --- backend/controllers/user.controller.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/backend/controllers/user.controller.js b/backend/controllers/user.controller.js index d6bad1e1d..9fbb64eb1 100644 --- a/backend/controllers/user.controller.js +++ b/backend/controllers/user.controller.js @@ -259,4 +259,28 @@ UserController.logout = async function (req, res) { return res.clearCookie('token').status(200).send('Successfully logged out.'); }; +UserController.updateManagedProjects = async function (req, res) { + const { headers } = req; + const { userId } = req.params; + const { projectId } = req.body; + + if (headers['x-customrequired-header'] !== expectedHeader) { + return res.sendStatus(403); + } + + // Update the managedProjects array for the user + try { + const user = await User.findOneAndUpdate( + { _id: userId }, + { managedProjects }, + { new: true } + ); + return res.status(200).send(user); + } catch (err) { + console.log(err); + return res.sendStatus(400); + } + +}; + module.exports = UserController; From 248a76da080d4720b8ff0d36e9cad7d41c9346de Mon Sep 17 00:00:00 2001 From: JamesNg Date: Wed, 23 Jul 2025 16:26:24 -0400 Subject: [PATCH 2/6] create new routes and controller methods to update user & proj managedby fields --- backend/controllers/project.controller.js | 38 +++++++++++++++++- backend/controllers/user.controller.js | 40 ++++++++++++++----- backend/routers/projects.router.js | 3 +- backend/routers/users.router.js | 3 ++ client/src/api/ProjectApiService.js | 21 ++++++++++ client/src/api/UserApiService.js | 24 +++++++++-- .../src/components/user-admin/EditUsers.jsx | 6 ++- client/src/pages/UserAdmin.jsx | 4 +- 8 files changed, 119 insertions(+), 20 deletions(-) diff --git a/backend/controllers/project.controller.js b/backend/controllers/project.controller.js index 7af1e99c1..9b44e7d34 100644 --- a/backend/controllers/project.controller.js +++ b/backend/controllers/project.controller.js @@ -1,4 +1,4 @@ -const { Project } = require('../models'); +const { Project, User } = require('../models'); const ProjectController = {}; @@ -66,4 +66,40 @@ ProjectController.destroy = async function (req, res) { } }; +ProjectController.updateManagedByUsers = async function (req, res) { + const { projectId } = req.params; + const { action, userId } = req.body; // action - 'add' or 'remove' + + try { + // Update project's managedByUsers and the user's managedProjects + const project = await Project.findById(projectId); + let managedByUsers = project.managedByUsers || []; + + const user = await User.findById(userId); + let managedProjects = user.managedProjects || []; + + if (action === 'add') { + managedByUsers = [...managedByUsers, userId]; + managedProjects = [...managedProjects, projectId]; + } else { + // remove case + managedByUsers = managedByUsers.filter((id) => id !== userId); + managedProjects = managedProjects.filter((id) => id !== projectId); + } + + // Update project's managedByUsers + project.managedByUsers = managedByUsers; + await project.save({ validateBeforeSave: false }); + + // Update user's managedProjects + user.managedProjects = managedProjects; + await user.save({ validateBeforeSave: false }); + + return res.status(200).send({ project, user }); + } catch (err) { + console.log(err); + return res.sendStatus(400); + } +}; + module.exports = ProjectController; diff --git a/backend/controllers/user.controller.js b/backend/controllers/user.controller.js index 9fbb64eb1..60b9bde14 100644 --- a/backend/controllers/user.controller.js +++ b/backend/controllers/user.controller.js @@ -232,7 +232,6 @@ UserController.signin = function (req, res) { }; UserController.verifySignIn = async function (req, res) { - let token = req.headers['x-access-token'] || req.headers['authorization']; if (token.startsWith('Bearer ')) { // Remove Bearer from string @@ -259,28 +258,47 @@ UserController.logout = async function (req, res) { return res.clearCookie('token').status(200).send('Successfully logged out.'); }; +// Update user's managedProjects UserController.updateManagedProjects = async function (req, res) { const { headers } = req; - const { userId } = req.params; - const { projectId } = req.body; + const { UserId } = req.params; + const { action, projectId } = req.body; // action - 'add' or 'remove' + // console.log('action:', action, 'projectId:', projectId); if (headers['x-customrequired-header'] !== expectedHeader) { return res.sendStatus(403); } - // Update the managedProjects array for the user try { - const user = await User.findOneAndUpdate( - { _id: userId }, - { managedProjects }, - { new: true } - ); - return res.status(200).send(user); + // Update user's managedProjects and the project's managedByUsers + const user = await User.findById(UserId); + let managedProjects = user.managedProjects || []; + + const project = await Project.findById(projectId); + let managedByUsers = project.managedByUsers || []; + + if (action === 'add') { + managedProjects = [...managedProjects, projectId]; + managedByUsers = [...managedByUsers, UserId]; + } else { + // remove case + managedProjects = managedProjects.filter((id) => id !== projectId); + managedByUsers = managedByUsers.filter((id) => id !== UserId); + } + + // Update user's managedProjects + user.managedProjects = managedProjects; + await user.save({ validateBeforeSave: false }); + + // Update project's managedByUsers + project.managedByUsers = managedByUsers; + await project.save({ validateBeforeSave: false }); + + return res.status(200).send({ user, project }); } catch (err) { console.log(err); return res.sendStatus(400); } - }; module.exports = UserController; diff --git a/backend/routers/projects.router.js b/backend/routers/projects.router.js index 16aed3728..19acaa43d 100644 --- a/backend/routers/projects.router.js +++ b/backend/routers/projects.router.js @@ -16,6 +16,7 @@ router.get('/:ProjectId', ProjectController.project_by_id); router.put('/:ProjectId', AuthUtil.verifyCookie, ProjectController.update); -router.patch('/:ProjectId', AuthUtil.verifyCookie, ProjectController.update); +// Update project's managedByUsers in db +router.patch('/:ProjectId', AuthUtil.verifyCookie, ProjectController.updateManagedByUsers); module.exports = router; diff --git a/backend/routers/users.router.js b/backend/routers/users.router.js index a20da8f5a..832c9edf0 100644 --- a/backend/routers/users.router.js +++ b/backend/routers/users.router.js @@ -16,6 +16,9 @@ router.get('/:UserId', UserController.user_by_id); router.patch('/:UserId', UserController.update); +// Update user projects in db +router.patch('/:UserId/managedProjects', UserController.updateManagedProjects); + router.delete('/:UserId', UserController.delete); module.exports = router; diff --git a/client/src/api/ProjectApiService.js b/client/src/api/ProjectApiService.js index b2e692462..80dfc8523 100644 --- a/client/src/api/ProjectApiService.js +++ b/client/src/api/ProjectApiService.js @@ -84,6 +84,27 @@ class ProjectApiService { } } + // update managedByUsers in Project + async updateManagedByUsers(projectId, userId, action) { + const url = `${this.baseProjectUrl}${projectId}`; + const requestOptions = { + method: 'PATCH', + headers: this.headers, + body: JSON.stringify({ action, userId }), + }; + + try { + const response = await fetch(url, requestOptions); + const resJson = await response.json(); + console.log(resJson); + return resJson; + } catch (error) { + console.log(`update project error: `, error); + alert('Server not responding. Please try again.'); + return undefined; + } + } + async fetchPMProjects(projects) { const requestOptions = { headers: this.headers, diff --git a/client/src/api/UserApiService.js b/client/src/api/UserApiService.js index 3ef938f02..78eba0ceb 100644 --- a/client/src/api/UserApiService.js +++ b/client/src/api/UserApiService.js @@ -51,13 +51,13 @@ class UserApiService { } // Updates user projects in db - async updateUserDbProjects(userToEdit, managedProjects) { + async updateUserDbProjects(userToEdit, projectId, action) { // eslint-disable-next-line no-underscore-dangle - const url = `${this.baseUserUrl}${userToEdit._id}`; + const url = `${this.baseUserUrl}${userToEdit._id}/managedProjects`; const requestOptions = { method: 'PATCH', headers: this.headers, - body: JSON.stringify({ managedProjects }), + body: JSON.stringify({ action, projectId }), }; try { @@ -68,6 +68,24 @@ class UserApiService { } return undefined; } + // // Updates user projects in db + // async updateUserDbProjects(userToEdit, managedProjects) { + // // eslint-disable-next-line no-underscore-dangle + // const url = `${this.baseUserUrl}${userToEdit._id}`; + // const requestOptions = { + // method: 'PATCH', + // headers: this.headers, + // body: JSON.stringify({ managedProjects }), + // }; + + // try { + // return await fetch(url, requestOptions); + // } catch (error) { + // console.log(`update user error: `, error); + // alert('Server not responding. Please try again.'); + // } + // return undefined; + // } async updateUserDbIsActive(userToEdit, isActive) { const url = `${this.baseUserUrl}${userToEdit._id}`; diff --git a/client/src/components/user-admin/EditUsers.jsx b/client/src/components/user-admin/EditUsers.jsx index 825ee23dd..e5d48075d 100644 --- a/client/src/components/user-admin/EditUsers.jsx +++ b/client/src/components/user-admin/EditUsers.jsx @@ -61,7 +61,8 @@ const EditUsers = ({ !userManagedProjects.includes(projectValue) ) { const newProjects = [...userManagedProjects, projectValue]; - updateUserDb(userToEdit, newProjects); + updateUserDb(userToEdit, projectValue, 'add'); + // updateUserDb(userToEdit, newProjects); setUserManagedProjects(newProjects); setProjectValue(''); } else { @@ -74,7 +75,8 @@ const EditUsers = ({ const newProjects = userManagedProjects.filter( (p) => p !== projectToRemove ); - updateUserDb(userToEdit, newProjects); + updateUserDb(userToEdit, projectToRemove, 'remove'); + // updateUserDb(userToEdit, newProjects); setUserManagedProjects(newProjects); } }; diff --git a/client/src/pages/UserAdmin.jsx b/client/src/pages/UserAdmin.jsx index 460f53aa7..586f3252e 100644 --- a/client/src/pages/UserAdmin.jsx +++ b/client/src/pages/UserAdmin.jsx @@ -20,8 +20,8 @@ const UserAdmin = () => { }, [userApiService]); const updateUserDb = useCallback( - async (user, managedProjects) => { - await userApiService.updateUserDbProjects(user, managedProjects); + async (user, projectId, action) => { + await userApiService.updateUserDbProjects(user, projectId, action); fetchUsers(); }, [userApiService, fetchUsers] From 642adf6e774de7bfeda077c880570503d340862e Mon Sep 17 00:00:00 2001 From: JamesNg Date: Tue, 29 Jul 2025 14:32:53 -0400 Subject: [PATCH 3/6] add unit testing for functions --- backend/controllers/project.controller.js | 8 +- backend/routers/projects.router.test.js | 122 +++++++++++++++++++++- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/backend/controllers/project.controller.js b/backend/controllers/project.controller.js index 9b44e7d34..b8672d9bc 100644 --- a/backend/controllers/project.controller.js +++ b/backend/controllers/project.controller.js @@ -67,12 +67,12 @@ ProjectController.destroy = async function (req, res) { }; ProjectController.updateManagedByUsers = async function (req, res) { - const { projectId } = req.params; + const { ProjectId } = req.params; const { action, userId } = req.body; // action - 'add' or 'remove' try { // Update project's managedByUsers and the user's managedProjects - const project = await Project.findById(projectId); + const project = await Project.findById(ProjectId); let managedByUsers = project.managedByUsers || []; const user = await User.findById(userId); @@ -80,11 +80,11 @@ ProjectController.updateManagedByUsers = async function (req, res) { if (action === 'add') { managedByUsers = [...managedByUsers, userId]; - managedProjects = [...managedProjects, projectId]; + managedProjects = [...managedProjects, ProjectId]; } else { // remove case managedByUsers = managedByUsers.filter((id) => id !== userId); - managedProjects = managedProjects.filter((id) => id !== projectId); + managedProjects = managedProjects.filter((id) => id !== ProjectId); } // Update project's managedByUsers diff --git a/backend/routers/projects.router.test.js b/backend/routers/projects.router.test.js index c5c9f30c0..f9376ea92 100644 --- a/backend/routers/projects.router.test.js +++ b/backend/routers/projects.router.test.js @@ -158,7 +158,7 @@ describe('Unit testing for Projects router', () => { // Tests expect(ProjectController.create).toHaveBeenCalledWith( - expect.objectContaining({ body: newProject }), // Check if newProject in body is parsed + expect.objectContaining({ body: newProject }), // Check if newProject in body is parsed expect.anything(), // Mock response expect.anything(), // Mock next ); @@ -234,7 +234,7 @@ describe('Unit testing for Projects router', () => { }); const updatedProject = { - id: '1', + id: 'projectId1', name: 'updated project1', description: 'updated testing', githubIdentifier: 'gitHubTest3', @@ -251,7 +251,7 @@ describe('Unit testing for Projects router', () => { lookingDescription: 'n/a', recruitingCategories: ['n/a'], partners: ['n/a'], - managedByUsers: ['n/a'], + managedByUsers: ['userId1'], }; const ProjectId = updatedProject.id; @@ -274,7 +274,7 @@ describe('Unit testing for Projects router', () => { // Tests expect(ProjectController.update).toHaveBeenCalledWith( - expect.objectContaining({ params: { ProjectId }}), // Check if ProjectId is parsed from params + expect.objectContaining({ params: { ProjectId } }), // Check if ProjectId is parsed from params expect.anything(), // Mock response expect.anything(), // Mock next ); @@ -284,5 +284,119 @@ describe('Unit testing for Projects router', () => { // Marks completion of tests done(); }); + + const updatedUser = { + id: 'userId1', + name: 'Updated User', + email: 'mockuser@example.com', + managedProjects: ['projectId1'], + }; + + const userId = updatedUser.id; + + it("should add to the project's managedByUsers and the user's managedProjects fields with PATCH /api/projects/:ProjectId", async (done) => { + // Mock ProjectController.updateManagedByUsers method when this route is called + ProjectController.updateManagedByUsers.mockImplementationOnce((req, res) => { + res.status(200).send({ project: updatedProject, user: updatedUser }); + }); + + // Mock PUT API call + const response = await request + .patch(`/api/projects/${ProjectId}`) + .send({ action: 'add', userId }); + + // Middlware assertions + expect(mockVerifyCookie).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + expect.any(Function), + ); + + // Tests + expect(ProjectController.updateManagedByUsers).toHaveBeenCalledWith( + expect.objectContaining({ + params: { ProjectId }, + body: { action: 'add', userId }, + }), // Check if ProjectId is parsed from params + expect.anything(), // Mock response + expect.anything(), // Mock next + ); + expect(response.status).toBe(200); + expect(response.body).toEqual({ project: updatedProject, user: updatedUser }); + + // Marks completion of tests + done(); + }); + + it("should add to the project's managedByUsers and the user's managedProjects fields with PATCH /api/projects/:ProjectId", async (done) => { + // Mock ProjectController.updateManagedByUsers method when this route is called + ProjectController.updateManagedByUsers.mockImplementationOnce((req, res) => { + res.status(200).send({ project: updatedProject, user: updatedUser }); + }); + + // Mock PUT API call + const response = await request + .patch(`/api/projects/${ProjectId}`) + .send({ action: 'add', userId }); + + // Middlware assertions + expect(mockVerifyCookie).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + expect.any(Function), + ); + + // Tests + expect(ProjectController.updateManagedByUsers).toHaveBeenCalledWith( + expect.objectContaining({ + params: { ProjectId }, + body: { action: 'add', userId }, + }), // Check if ProjectId is parsed from params + expect.anything(), // Mock response + expect.anything(), // Mock next + ); + expect(response.status).toBe(200); + expect(response.body).toEqual({ project: updatedProject, user: updatedUser }); + + // Marks completion of tests + done(); + }); + + it("should remove user from the project's managedByUsers and remove project from the user's managedProjects fields with PATCH /api/projects/:ProjectId", async (done) => { + updatedProject.managedByUsers = []; + updatedUser.managedProjects = []; + + // Mock ProjectController.updateManagedByUsers method when this route is called + ProjectController.updateManagedByUsers.mockImplementationOnce((req, res) => { + res.status(200).send({ project: updatedProject, user: updatedUser }); + }); + + // Mock PUT API call + const response = await request + .patch(`/api/projects/${ProjectId}`) + .send({ action: 'remove', userId }); + + // Middlware assertions + expect(mockVerifyCookie).toHaveBeenCalledWith( + expect.any(Object), + expect.any(Object), + expect.any(Function), + ); + + // Tests + expect(ProjectController.updateManagedByUsers).toHaveBeenCalledWith( + expect.objectContaining({ + params: { ProjectId }, + body: { action: 'remove', userId }, + }), // Check if ProjectId is parsed from params + expect.anything(), // Mock response + expect.anything(), // Mock next + ); + expect(response.status).toBe(200); + expect(response.body).toEqual({ project: updatedProject, user: updatedUser }); + + // Marks completion of tests + done(); + }); }); }); From bf6d38afc5950251316c7e7600d7955f15e767c3 Mon Sep 17 00:00:00 2001 From: JamesNg Date: Tue, 29 Jul 2025 17:02:27 -0400 Subject: [PATCH 4/6] add and update unit testing for user router --- backend/routers/users.router.test.js | 440 +++++++++++++++------------ 1 file changed, 244 insertions(+), 196 deletions(-) diff --git a/backend/routers/users.router.test.js b/backend/routers/users.router.test.js index b8f65e201..9fb96062f 100644 --- a/backend/routers/users.router.test.js +++ b/backend/routers/users.router.test.js @@ -15,206 +15,254 @@ testapp.use('/api/users', usersRouter); const request = supertest(testapp); describe('Unit Tests for userRouter', () => { - // Mocked user data - const mockUser = { - name: { - firstName: 'test', - lastName: 'user', - }, - email: 'newtest@test.com', - }; - const mockId = '12345'; - const mockUpdatedEmail = { - email: 'newtest@test.com', - }; + // Mocked user data + const mockUser = { + id: 'userId1', + name: { + firstName: 'test', + lastName: 'user', + }, + email: 'newtest@test.com', + accessLevel: 'admin', + managedProjects: ['projectId1'], + }; + const mockUserId = mockUser.id; + const mockUpdatedEmail = mockUser.email; + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('CREATE', () => { + it('should create a User through the UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.create.mockImplementationOnce((req, res) => { + return res.status(201).send(mockUser); + }); - afterEach(() => { - jest.clearAllMocks(); + //Functionality + //Post mockUser to CREATE API Endpoint + const response = await request.post('/api/users/').send(mockUser); + + //Test + expect(UserController.create).toHaveBeenCalledWith( + expect.objectContaining({ body: mockUser }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(201); + expect(response.body).toEqual(mockUser); + + done(); }); + }); + + describe('READ', () => { + it('should get a list of Users with with GET to /api/users/ through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.user_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); - describe('CREATE', () => { - it('should create a User through the UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.create.mockImplementationOnce( - (req, res) => { return res.status(201).send(mockUser) } - ); - - //Functionality - //Post mockUser to CREATE API Endpoint - const response = await request - .post('/api/users/') - .send(mockUser); - - //Test - expect(UserController.create).toHaveBeenCalledWith( - expect.objectContaining({body: mockUser}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(201); - expect(response.body).toEqual(mockUser); - - done(); - }); + //Functionality + //Get list of all users from READ API Endpoint + const response = await request.get('/api/users/'); + + //Test + expect(UserController.user_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); }); - - describe('READ', () => { - it('should get a list of Users with with GET to /api/users/ through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.user_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get list of all users from READ API Endpoint - const response = await request - .get('/api/users/'); - - //Test - expect(UserController.user_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a specific User by param with GET to /api/users?email= through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.user_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get a user with a specific email using a query param to READ API Endpoint - const response = await request - .get('/api/users?email=newtest@test.com'); - - //Test - expect(UserController.user_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a list of Users with accessLevel of admin or superadmin with GET to /api/users/admins through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.admin_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get a list of admins and superadmins from READ API Endpoint for admins - const response = await request - .get('/api/users/admins'); - - //Test - expect(UserController.admin_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a list of Users with the ability to manage projects with GET to /api/users/projectManagers through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.projectLead_list.mockImplementationOnce( - (req, res) => { return res.status(200).send([mockUser]) } - ); - - //Functionality - //Get a list of project leads and admins from READ API Endpoint for project leads - const response = await request - .get('/api/users/projectManagers'); - - //Test - expect(UserController.projectLead_list).toHaveBeenCalled(); - expect(response.status).toBe(200); - expect(response.body[0]).toEqual(mockUser); - - done(); - }); - - it('should get a specific User by UserId with GET to /api/users/:UserId through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.user_by_id.mockImplementationOnce( - (req, res) => { return res.status(200).send(mockUser) } - ); - - //Functionality - //Get a specific user from READ API Endpoint for specific UUIDs - const response = await request - .get(`/api/users/${mockId}`); - - //Test - expect(UserController.user_by_id).toHaveBeenCalledWith( - expect.objectContaining({params: {UserId: mockId}}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(200); - expect(response.body).toEqual(mockUser); - - done(); - }); + + it('should get a specific User by param with GET to /api/users?email= through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.user_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get a user with a specific email using a query param to READ API Endpoint + const response = await request.get('/api/users?email=newtest@test.com'); + + //Test + expect(UserController.user_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); }); - - describe('UPDATE', () => { - it('should update a User with PATCH to /api/users/:UserId through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.update.mockImplementationOnce( - (req, res) => { return res.status(200).send(mockUser) } - ); - - //Functionality - //Patch a user with a specific id by sending new user data to UPDATE API Endpoint - const response = await request - .patch(`/api/users/${mockId}`) - .send(mockUpdatedEmail); - - //Test - expect(UserController.update).toHaveBeenCalledWith( - expect.objectContaining({params: {UserId: mockId}}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(200); - expect(response.body).toEqual(mockUser); - - done(); - }); + + it('should get a list of Users with accessLevel of admin or superadmin with GET to /api/users/admins through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.admin_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get a list of admins and superadmins from READ API Endpoint for admins + const response = await request.get('/api/users/admins'); + + //Test + expect(UserController.admin_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); + }); + + it('should get a list of Users with the ability to manage projects with GET to /api/users/projectManagers through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.projectManager_list.mockImplementationOnce((req, res) => { + return res.status(200).send([mockUser]); + }); + + //Functionality + //Get a list of project leads and admins from READ API Endpoint for project leads + const response = await request.get('/api/users/projectManagers'); + + //Test + expect(UserController.projectManager_list).toHaveBeenCalled(); + expect(response.status).toBe(200); + expect(response.body[0]).toEqual(mockUser); + + done(); }); - - describe('DELETE', () => { - it('should delete a specific user by Id with DELETE /api/users/:UserId through UserController', async (done) => { - //Setup - //Mock the UserController function that this route calls with expected results - UserController.delete.mockImplementationOnce( - (req, res) => { return res.status(200).send(mockUser) } - ); - - //Delete user with a specific id via a request to DELETE API Endpoint - const response = await request - .delete(`/api/users/${mockId}`) - .send(mockUpdatedEmail); - - //Test - expect(UserController.delete).toHaveBeenCalledWith( - expect.objectContaining({params: {UserId: mockId}}), - expect.anything(), // Mock the response object - expect.anything() // Mock the next function - ); - expect(response.status).toBe(200); - expect(response.body).toEqual(mockUser); - - done(); - }); + + it('should get a specific User by UserId with GET to /api/users/:UserId through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.user_by_id.mockImplementationOnce((req, res) => { + return res.status(200).send(mockUser); + }); + + //Functionality + //Get a specific user from READ API Endpoint for specific UUIDs + const response = await request.get(`/api/users/${mockUserId}`); + + //Test + expect(UserController.user_by_id).toHaveBeenCalledWith( + expect.objectContaining({ params: { UserId: mockUserId } }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockUser); + + done(); + }); + }); + + describe('UPDATE', () => { + it('should update a User with PATCH to /api/users/:UserId through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.update.mockImplementationOnce((req, res) => { + return res.status(200).send(mockUser); + }); + + //Functionality + //Patch a user with a specific id by sending new user data to UPDATE API Endpoint + const response = await request.patch(`/api/users/${mockUserId}`).send(mockUpdatedEmail); + + //Test + expect(UserController.update).toHaveBeenCalledWith( + expect.objectContaining({ params: { UserId: mockUserId } }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockUser); + + done(); + }); + + // Create mock project and add userId to managedByUsers + const mockProject = { + id: 'projectId1', + name: 'Test Project', + managedByUsers: [mockUserId], + }; + const projectId = mockProject.id; + + it("should add projectId to user's managedProjects and userId to project's managedByUsers with PATCH /api/users/:UserId/managedProjects", async (done) => { + // Mock the response of UserController.updateManagedProjects + UserController.updateManagedProjects.mockImplementationOnce((req, res) => { + return res.status(200).send({ user: mockUser, project: mockProject }); + }); + + // Send PATCH request to update managedProjects + const response = await request.patch(`/api/users/${mockUserId}/managedProjects`).send({ + action: 'add', + projectId: projectId, + }); + + // Tests + expect(UserController.updateManagedProjects).toHaveBeenCalledWith( + expect.objectContaining({ + params: { UserId: mockUserId }, + body: { action: 'add', projectId }, + }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual({ user: mockUser, project: mockProject }); + + done(); + }); + + it("should remove projectId in user's managedProjects and userId in project's managedByUsers with PATCH /api/users/:UserId/managedProjects", async (done) => { + // Remove projectId and userId from fields + mockProject.managedByUsers = []; + mockUser.managedProjects = []; + + // Mock the response of UserController.updateManagedProjects + UserController.updateManagedProjects.mockImplementationOnce((req, res) => { + return res.status(200).send({ user: mockUser, project: mockProject }); + }); + + // Send PATCH request to update managedProjects + const response = await request.patch(`/api/users/${mockUserId}/managedProjects`).send({ + action: 'remove', + projectId: projectId, + }); + + // Tests + expect(UserController.updateManagedProjects).toHaveBeenCalledWith( + expect.objectContaining({ + params: { UserId: mockUserId }, + body: { action: 'remove', projectId }, + }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual({ user: mockUser, project: mockProject }); + + done(); + }); + }); + + describe('DELETE', () => { + it('should delete a specific user by Id with DELETE /api/users/:UserId through UserController', async (done) => { + //Mock the UserController function that this route calls with expected results + UserController.delete.mockImplementationOnce((req, res) => { + return res.status(200).send(mockUser); + }); + + //Delete user with a specific id via a request to DELETE API Endpoint + const response = await request.delete(`/api/users/${mockUserId}`).send(mockUpdatedEmail); + + //Test + expect(UserController.delete).toHaveBeenCalledWith( + expect.objectContaining({ params: { UserId: mockUserId } }), + expect.anything(), // Mock the response object + expect.anything(), // Mock the next function + ); + expect(response.status).toBe(200); + expect(response.body).toEqual(mockUser); + + done(); }); -}); \ No newline at end of file + }); +}); From ae699d19cae669e0e0373bc3913e2da696093a14 Mon Sep 17 00:00:00 2001 From: JamesNg Date: Mon, 22 Sep 2025 21:42:49 -0400 Subject: [PATCH 5/6] remove duplicate test and update projectController fn --- backend/controllers/project.controller.js | 4 +-- backend/routers/projects.router.test.js | 34 ----------------------- 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/backend/controllers/project.controller.js b/backend/controllers/project.controller.js index b8672d9bc..db017c890 100644 --- a/backend/controllers/project.controller.js +++ b/backend/controllers/project.controller.js @@ -79,8 +79,8 @@ ProjectController.updateManagedByUsers = async function (req, res) { let managedProjects = user.managedProjects || []; if (action === 'add') { - managedByUsers = [...managedByUsers, userId]; - managedProjects = [...managedProjects, ProjectId]; + managedByUsers = [...new Set([...managedByUsers, userId])]; + managedProjects = [...new Set([...managedProjects, ProjectId])]; } else { // remove case managedByUsers = managedByUsers.filter((id) => id !== userId); diff --git a/backend/routers/projects.router.test.js b/backend/routers/projects.router.test.js index f9376ea92..100e59635 100644 --- a/backend/routers/projects.router.test.js +++ b/backend/routers/projects.router.test.js @@ -328,40 +328,6 @@ describe('Unit testing for Projects router', () => { done(); }); - it("should add to the project's managedByUsers and the user's managedProjects fields with PATCH /api/projects/:ProjectId", async (done) => { - // Mock ProjectController.updateManagedByUsers method when this route is called - ProjectController.updateManagedByUsers.mockImplementationOnce((req, res) => { - res.status(200).send({ project: updatedProject, user: updatedUser }); - }); - - // Mock PUT API call - const response = await request - .patch(`/api/projects/${ProjectId}`) - .send({ action: 'add', userId }); - - // Middlware assertions - expect(mockVerifyCookie).toHaveBeenCalledWith( - expect.any(Object), - expect.any(Object), - expect.any(Function), - ); - - // Tests - expect(ProjectController.updateManagedByUsers).toHaveBeenCalledWith( - expect.objectContaining({ - params: { ProjectId }, - body: { action: 'add', userId }, - }), // Check if ProjectId is parsed from params - expect.anything(), // Mock response - expect.anything(), // Mock next - ); - expect(response.status).toBe(200); - expect(response.body).toEqual({ project: updatedProject, user: updatedUser }); - - // Marks completion of tests - done(); - }); - it("should remove user from the project's managedByUsers and remove project from the user's managedProjects fields with PATCH /api/projects/:ProjectId", async (done) => { updatedProject.managedByUsers = []; updatedUser.managedProjects = []; From 432ed7e4edea6a78c9ebe8a2c690277fb8c09c23 Mon Sep 17 00:00:00 2001 From: JamesNg Date: Mon, 22 Sep 2025 22:15:32 -0400 Subject: [PATCH 6/6] remove comment from userapiservice --- client/src/api/UserApiService.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/client/src/api/UserApiService.js b/client/src/api/UserApiService.js index 78eba0ceb..0c4e115d4 100644 --- a/client/src/api/UserApiService.js +++ b/client/src/api/UserApiService.js @@ -68,24 +68,6 @@ class UserApiService { } return undefined; } - // // Updates user projects in db - // async updateUserDbProjects(userToEdit, managedProjects) { - // // eslint-disable-next-line no-underscore-dangle - // const url = `${this.baseUserUrl}${userToEdit._id}`; - // const requestOptions = { - // method: 'PATCH', - // headers: this.headers, - // body: JSON.stringify({ managedProjects }), - // }; - - // try { - // return await fetch(url, requestOptions); - // } catch (error) { - // console.log(`update user error: `, error); - // alert('Server not responding. Please try again.'); - // } - // return undefined; - // } async updateUserDbIsActive(userToEdit, isActive) { const url = `${this.baseUserUrl}${userToEdit._id}`;