diff --git a/src/controllers/userProfileController.deleteUserProfile.spec.js b/src/controllers/userProfileController.deleteUserProfile.spec.js new file mode 100644 index 000000000..bdd82565c --- /dev/null +++ b/src/controllers/userProfileController.deleteUserProfile.spec.js @@ -0,0 +1,100 @@ +jest.mock('../helpers/userHelper', () => () => ({})); +jest.mock('../models/timeentry', () => ({})); +jest.mock('../models/team', () => ({ + updateMany: jest.fn(), +})); +jest.mock('../models/badge', () => ({})); +jest.mock('../utilities/nodeCache', () => + jest.fn(() => ({ + removeCache: jest.fn(), + getCache: jest.fn(() => null), + setCache: jest.fn(), + })), +); +jest.mock('../models/followUp', () => ({ + findOneAndDelete: jest.fn(), +})); +jest.mock('../models/task', () => ({ + updateMany: jest.fn(), +})); +jest.mock('../models/hgnFormResponse', () => ({})); +jest.mock('../services/userService', () => ({})); +jest.mock('../utilities/permissions', () => ({ + hasPermission: jest.fn(), + canRequestorUpdateUser: jest.fn(), +})); +jest.mock('../utilities/emailSender', () => jest.fn()); +jest.mock('../utilities/objectUtils', () => ({ + deepCopyMongooseObjectWithLodash: jest.fn((value) => value), +})); +jest.mock('../startup/logger', () => ({ + logInfo: jest.fn(), + logException: jest.fn(), +})); +jest.mock('./reportsController', () => () => ({})); + +const mongoose = require('mongoose'); +const Team = require('../models/team'); +const Task = require('../models/task'); +const followUp = require('../models/followUp'); +const { hasPermission, canRequestorUpdateUser } = require('../utilities/permissions'); +const { mockReq, mockRes } = require('../test'); +const userProfileController = require('./userProfileController'); + +describe('userProfileController deleteUserProfile', () => { + const userId = '65cf6c3706d8ac105827bb2e'; + const mockUserProfileModel = { + findById: jest.fn(), + deleteOne: jest.fn(), + }; + + const makeSut = () => userProfileController(mockUserProfileModel, {}); + + beforeEach(() => { + jest.clearAllMocks(); + hasPermission.mockResolvedValue(true); + canRequestorUpdateUser.mockResolvedValue(true); + mockUserProfileModel.findById.mockResolvedValue({ + _id: userId, + email: 'volunteer@example.org', + }); + mockUserProfileModel.deleteOne.mockResolvedValue({ deletedCount: 1 }); + followUp.findOneAndDelete.mockResolvedValue({}); + Task.updateMany.mockResolvedValue({ modifiedCount: 1 }); + Team.updateMany.mockResolvedValue({ modifiedCount: 1 }); + mockRes.status.mockReturnThis(); + + mockReq.body = { + ...mockReq.body, + userId, + option: 'delete', + role: 'Volunteer', + requestor: { + ...mockReq.body.requestor, + requestorId: '507f1f77bcf86cd799439011', + }, + }; + }); + + test('removes deleted users from tasks and teams using both string and ObjectId matches', async () => { + const { deleteUserProfile } = makeSut(); + + await deleteUserProfile(mockReq, mockRes); + + const expectedObjectId = new mongoose.Types.ObjectId(userId); + const expectedMatchedUserIds = [userId, expectedObjectId]; + + expect(mockUserProfileModel.deleteOne).toHaveBeenCalledWith({ _id: userId }); + expect(followUp.findOneAndDelete).toHaveBeenCalledWith({ userId }); + expect(Task.updateMany).toHaveBeenCalledWith( + { 'resources.userID': { $in: expectedMatchedUserIds } }, + { $pull: { resources: { userID: { $in: expectedMatchedUserIds } } } }, + ); + expect(Team.updateMany).toHaveBeenCalledWith( + { 'members.userId': { $in: expectedMatchedUserIds } }, + { $pull: { members: { userId: { $in: expectedMatchedUserIds } } } }, + ); + expect(mockRes.status).toHaveBeenCalledWith(200); + expect(mockRes.send).toHaveBeenCalledWith({ message: 'Executed Successfully' }); + }); +}); diff --git a/src/controllers/userProfileController.js b/src/controllers/userProfileController.js index 412b0a42d..cc9de5f5b 100644 --- a/src/controllers/userProfileController.js +++ b/src/controllers/userProfileController.js @@ -21,6 +21,7 @@ const Badge = require('../models/badge'); const yearMonthDayDateValidator = require('../utilities/yearMonthDayDateValidator'); const cacheClosure = require('../utilities/nodeCache'); const followUp = require('../models/followUp'); +const Task = require('../models/task'); const HGNFormResponses = require('../models/hgnFormResponse'); const userService = require('../services/userService'); const { hasPermission, canRequestorUpdateUser } = require('../utilities/permissions'); @@ -1427,6 +1428,20 @@ const createControllerMethods = function (UserProfile, Project, cache) { await UserProfile.deleteOne({ _id: userId }); // delete followUp for deleted user await followUp.findOneAndDelete({ userId }); + const matchedUserIds = [userId]; + if (mongoose.Types.ObjectId.isValid(userId)) { + matchedUserIds.push(new mongoose.Types.ObjectId(userId)); + } + // delete user from task-resources + await Task.updateMany( + { 'resources.userID': { $in: matchedUserIds } }, + { $pull: { resources: { userID: { $in: matchedUserIds } } } }, + ); + // delete user from teams-members + await Team.updateMany( + { 'members.userId': { $in: matchedUserIds } }, + { $pull: { members: { userId: { $in: matchedUserIds } } } }, + ); res.status(200).send({ message: 'Executed Successfully' }); auditIfProtectedAccountUpdated({ requestorId: req.body.requestor.requestorId,