Skip to content
110 changes: 110 additions & 0 deletions src/controllers/userProfileController.deleteUserProfile.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
jest.mock('../helpers/userHelper', () => () => ({}));
jest.mock('jsonwebtoken', () => ({
sign: jest.fn(),
}));
jest.mock('../models/timeentry', () => ({}));
jest.mock('../models/team', () => ({
collection: {
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', () => ({
collection: {
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 userProfileController = require('./userProfileController');

describe('userProfileController deleteUserProfile', () => {
const userId = '65cf6c3706d8ac105827bb2e';
const mockUserProfileModel = {
findById: jest.fn(),
deleteOne: jest.fn(),
};
let mockReq;
let mockRes;

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.collection.updateMany.mockResolvedValue({ modifiedCount: 1 });
Team.collection.updateMany.mockResolvedValue({ modifiedCount: 1 });
mockReq = {
body: {
userId,
option: 'delete',
role: 'Volunteer',
requestor: {
requestorId: '507f1f77bcf86cd799439011',
},
},
};
mockRes = {
status: jest.fn().mockReturnThis(),
send: jest.fn(),
};
});

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.collection.updateMany).toHaveBeenCalledWith(
{ 'resources.userID': { $in: expectedMatchedUserIds } },
{ $pull: { resources: { userID: { $in: expectedMatchedUserIds } } } },
);
expect(Team.collection.updateMany).toHaveBeenCalledWith(
{ 'members.userId': { $in: expectedMatchedUserIds } },
{ $pull: { members: { userId: { $in: expectedMatchedUserIds } } } },
);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.send).toHaveBeenCalledWith({ message: 'Executed Successfully' });
});
});
24 changes: 18 additions & 6 deletions src/controllers/userProfileController.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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');
Expand Down Expand Up @@ -1164,14 +1165,9 @@

// Verify the update was successful by fetching the user directly from DB
const verificationUser = await UserProfile.findById(userId, 'bioPosted firstName lastName');
console.log(
`Database verification - User: ${verificationUser.firstName} ${verificationUser.lastName}, bioPosted: ${verificationUser.bioPosted}`,
);

if (verificationUser.bioPosted !== bioPosted) {
console.error(
`WARNING: Database update failed! Expected: ${bioPosted}, Actual: ${verificationUser.bioPosted}`,
);
console.error('WARNING: Database update failed for bio status.');
return res.status(500).json({ error: 'Failed to update bio status in database.' });
}

Expand Down Expand Up @@ -1427,6 +1423,22 @@
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));
}

await Promise.all([
// Use raw collection updates so string user ids in existing records are not cast away.
Task.collection.updateMany(
{ 'resources.userID': { $in: matchedUserIds } },
{ $pull: { resources: { userID: { $in: matchedUserIds } } } },
),
Team.collection.updateMany(
{ 'members.userId': { $in: matchedUserIds } },
{ $pull: { members: { userId: { $in: matchedUserIds } } } },
),
]);
res.status(200).send({ message: 'Executed Successfully' });
auditIfProtectedAccountUpdated({
requestorId: req.body.requestor.requestorId,
Expand Down Expand Up @@ -1587,7 +1599,7 @@

await user.save();

console.log(`✅ Saved ${key} in DB:`, user[key]);

Check warning on line 1602 in src/controllers/userProfileController.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not log user-controlled data.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HGNRest&issues=AZ3jHT_yxTbyeZtngzpz&open=AZ3jHT_yxTbyeZtngzpz&pullRequest=2149

// ================================
// CACHE INVALIDATION (MERGED)
Expand Down
Loading