From c561cf9bcd624484aa21022c5d55bf659f1a7fe6 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Fri, 30 Jan 2026 17:27:21 +0100 Subject: [PATCH] test: enhance change password and user profile tests with new scenarios and validations --- .../src/controllers/user.controller.ts | 124 ++++++------- .../api/users/change-password.test.ts | 166 ++++++++++++++++-- .../integration/api/users/get-profile.test.ts | 62 ++++++- 3 files changed, 270 insertions(+), 82 deletions(-) diff --git a/meet-ce/backend/src/controllers/user.controller.ts b/meet-ce/backend/src/controllers/user.controller.ts index 1ef4437f..5670d6f4 100644 --- a/meet-ce/backend/src/controllers/user.controller.ts +++ b/meet-ce/backend/src/controllers/user.controller.ts @@ -10,9 +10,9 @@ import { } from '../models/error.model.js'; import { LoggerService } from '../services/logger.service.js'; import { RequestSessionService } from '../services/request-session.service.js'; -import { TokenService } from '../services/token.service.js'; import { UserService } from '../services/user.service.js'; import { getBaseUrl } from '../utils/url.utils.js'; +import { TokenService } from '../services/token.service.js'; export const createUser = async (req: Request, res: Response) => { const userOptions = req.body as MeetUserOptions; @@ -76,47 +76,6 @@ export const getUser = async (req: Request, res: Response) => { } }; -export const deleteUser = async (req: Request, res: Response) => { - const { userId } = req.params; - - const logger = container.get(LoggerService); - logger.verbose(`Deleting user with ID '${userId}'`); - - try { - const userService = container.get(UserService); - await userService.deleteUser(userId); - return res.status(200).json({ message: `User '${userId}' deleted successfully` }); - } catch (error) { - handleError(res, error, 'deleting user'); - } -}; - -export const bulkDeleteUsers = async (req: Request, res: Response) => { - const { userIds } = req.query as { userIds: string[] }; - - const logger = container.get(LoggerService); - logger.verbose(`Deleting users: ${userIds}`); - - try { - const userService = container.get(UserService); - const { deleted, failed } = await userService.bulkDeleteUsers(userIds); - - // All users were successfully deleted - if (deleted.length > 0 && failed.length === 0) { - return res.status(200).json({ message: 'All users deleted successfully', deleted }); - } - - // Some or all users could not be deleted - return res.status(400).json({ - message: `${failed.length} user(s) could not be deleted`, - deleted, - failed - }); - } catch (error) { - handleError(res, error, 'deleting users'); - } -}; - export const getMe = (_req: Request, res: Response) => { const requestSessionService = container.get(RequestSessionService); const user = requestSessionService.getAuthenticatedUser(); @@ -150,26 +109,6 @@ export const resetUserPassword = async (req: Request, res: Response) => { } }; -export const updateUserRole = async (req: Request, res: Response) => { - const { userId } = req.params; - const { role } = req.body as { role: MeetUserRole }; - - const logger = container.get(LoggerService); - logger.verbose(`Admin updating role for user '${userId}' to '${role}'`); - - try { - const userService = container.get(UserService); - const user = await userService.changeUserRole(userId, role); - - return res.status(200).json({ - message: `Role for user '${userId}' updated successfully to '${role}'`, - user: userService.convertToDTO(user) - }); - } catch (error) { - handleError(res, error, 'updating user role'); - } -}; - export const changePassword = async (req: Request, res: Response) => { const requestSessionService = container.get(RequestSessionService); const user = requestSessionService.getAuthenticatedUser(); @@ -210,3 +149,64 @@ export const changePassword = async (req: Request, res: Response) => { handleError(res, error, 'changing password'); } }; + +export const updateUserRole = async (req: Request, res: Response) => { + const { userId } = req.params; + const { role } = req.body as { role: MeetUserRole }; + + const logger = container.get(LoggerService); + logger.verbose(`Admin updating role for user '${userId}' to '${role}'`); + + try { + const userService = container.get(UserService); + const user = await userService.changeUserRole(userId, role); + + return res.status(200).json({ + message: `Role for user '${userId}' updated successfully to '${role}'`, + user: userService.convertToDTO(user) + }); + } catch (error) { + handleError(res, error, 'updating user role'); + } +}; + +export const deleteUser = async (req: Request, res: Response) => { + const { userId } = req.params; + + const logger = container.get(LoggerService); + logger.verbose(`Deleting user with ID '${userId}'`); + + try { + const userService = container.get(UserService); + await userService.deleteUser(userId); + return res.status(200).json({ message: `User '${userId}' deleted successfully` }); + } catch (error) { + handleError(res, error, 'deleting user'); + } +}; + +export const bulkDeleteUsers = async (req: Request, res: Response) => { + const { userIds } = req.query as { userIds: string[] }; + + const logger = container.get(LoggerService); + logger.verbose(`Deleting users: ${userIds}`); + + try { + const userService = container.get(UserService); + const { deleted, failed } = await userService.bulkDeleteUsers(userIds); + + // All users were successfully deleted + if (deleted.length > 0 && failed.length === 0) { + return res.status(200).json({ message: 'All users deleted successfully', deleted }); + } + + // Some or all users could not be deleted + return res.status(400).json({ + message: `${failed.length} user(s) could not be deleted`, + deleted, + failed + }); + } catch (error) { + handleError(res, error, 'deleting users'); + } +}; diff --git a/meet-ce/backend/tests/integration/api/users/change-password.test.ts b/meet-ce/backend/tests/integration/api/users/change-password.test.ts index fb51b9d6..64e3a689 100644 --- a/meet-ce/backend/tests/integration/api/users/change-password.test.ts +++ b/meet-ce/backend/tests/integration/api/users/change-password.test.ts @@ -1,35 +1,175 @@ -import { beforeAll, describe, expect, it } from '@jest/globals'; +import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; +import { MeetUserRole } from '@openvidu-meet/typings'; import { MEET_ENV } from '../../../../src/environment.js'; import { expectValidationError } from '../../../helpers/assertion-helpers.js'; -import { changePassword, loginAdminUser, startTestServer } from '../../../helpers/request-helpers.js'; +import { + changePassword, + changePasswordReq, + createRoom, + createUser, + deleteAllRooms, + deleteAllUsers, + loginReq, + loginRootAdmin, + loginUser, + refreshTokenReq, + startTestServer +} from '../../../helpers/request-helpers.js'; +import { setupUser } from '../../../helpers/test-scenarios.js'; describe('Users API Tests', () => { - let adminAccessToken: string; + let rootAdminAccessToken: string; beforeAll(async () => { await startTestServer(); - adminAccessToken = await loginAdminUser(); + ({ accessToken: rootAdminAccessToken } = await loginRootAdmin()); + }); + + afterAll(async () => { + await deleteAllRooms(); + await deleteAllUsers(); }); describe('Change Password Tests', () => { - it('should successfully change password', async () => { + it('should successfully change root admin password', async () => { const newPassword = 'newpassword123'; - const response = await changePassword(MEET_ENV.INITIAL_ADMIN_PASSWORD, newPassword, adminAccessToken); + const response = await changePasswordReq( + { currentPassword: MEET_ENV.INITIAL_ADMIN_PASSWORD, newPassword }, + rootAdminAccessToken + ); expect(response.status).toBe(200); + expect(response.body).toHaveProperty('message'); + expect(response.body.message).toContain('changed successfully'); + expect(response.body).not.toHaveProperty('accessToken'); + expect(response.body).not.toHaveProperty('refreshToken'); - // Reset password - await changePassword(newPassword, MEET_ENV.INITIAL_ADMIN_PASSWORD, adminAccessToken); + // Reset password back + await changePassword(newPassword, MEET_ENV.INITIAL_ADMIN_PASSWORD, rootAdminAccessToken); + }); + + it('should successfully login with new password after change', async () => { + const userId = MEET_ENV.INITIAL_ADMIN_USER; + const initialPassword = MEET_ENV.INITIAL_ADMIN_PASSWORD; + const newPassword = 'newpassword123'; + + // Change password + const changeResponse = await changePasswordReq( + { currentPassword: initialPassword, newPassword }, + rootAdminAccessToken + ); + expect(changeResponse.status).toBe(200); + + // Verify old password no longer works + const loginOldResponse = await loginReq({ + userId, + password: initialPassword + }); + expect(loginOldResponse.status).toBe(404); + + // Verify new password works + const loginResponse = await loginReq({ + userId, + password: newPassword + }); + expect(loginResponse.status).toBe(200); + + // Reset password back + await changePassword(newPassword, initialPassword, rootAdminAccessToken); + }); + + it('should successfully change password and return new tokens when mustChangePassword is true', async () => { + const userId = `user_${Date.now()}`; + const initialPassword = 'password123'; + const newPassword = 'NewPassword123!'; + + // Create user (when created, this user is set to require password change) + const createResponse = await createUser({ + userId, + name: 'Test User', + password: initialPassword, + role: MeetUserRole.USER + }); + expect(createResponse.status).toBe(201); + + // Login to get temporary token + const { accessToken: accessTokenTmp } = await loginUser(userId, initialPassword); + + // Change password + const response = await changePasswordReq({ currentPassword: initialPassword, newPassword }, accessTokenTmp); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('message'); + expect(response.body.message).toContain('changed successfully'); + expect(response.body).toHaveProperty('accessToken'); + expect(response.body).toHaveProperty('refreshToken'); + + const accessToken = response.body.accessToken; + const refreshToken = response.body.refreshToken; + + // Verify new access token work + await createRoom({}, `Bearer ${accessToken}`); + + // Verify new refresh token work + const refreshResponse = await refreshTokenReq(`Bearer ${refreshToken}`); + expect(refreshResponse.status).toBe(200); + }); + + it('should successfully change password for regular user without returning tokens', async () => { + const userData = await setupUser({ + userId: `user_${Date.now()}`, + name: 'Regular User', + password: 'password123', + role: MeetUserRole.USER + }); + + // Change password + const response = await changePasswordReq( + { currentPassword: userData.password, newPassword: 'newpassword123' }, + userData.accessToken + ); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('message'); + expect(response.body.message).toContain('changed successfully'); + expect(response.body).not.toHaveProperty('accessToken'); + expect(response.body).not.toHaveProperty('refreshToken'); }); it('should fail when current password is incorrect', async () => { - const response = await changePassword('wrongpassword', 'newpassword123', adminAccessToken); + const response = await changePasswordReq( + { currentPassword: 'wrongpassword', newPassword: 'newpassword123' }, + rootAdminAccessToken + ); expect(response.status).toBe(400); - expect(response.body).toHaveProperty('message', 'Invalid current password'); + expect(response.body).toHaveProperty('message'); + expect(response.body.message).toContain('Invalid current password'); + }); + }); + + describe('Change Password Validation Tests', () => { + it('should fail when new password is too short', async () => { + const response = await changePasswordReq( + { currentPassword: MEET_ENV.INITIAL_ADMIN_PASSWORD, newPassword: '1234' }, + rootAdminAccessToken + ); + expectValidationError(response, 'newPassword', 'New password must be at least 5 characters long'); }); - it('should fail when new password is not 5 characters long', async () => { - const response = await changePassword(MEET_ENV.INITIAL_ADMIN_PASSWORD, '1234', adminAccessToken); - expectValidationError(response, 'newPassword', 'New password must be at least 5 characters long'); + it('should fail when currentPassword is missing', async () => { + const response = await changePasswordReq( + { newPassword: 'newpassword123' } as { currentPassword: string; newPassword: string }, + rootAdminAccessToken + ); + expectValidationError(response, 'currentPassword', 'Required'); + }); + + it('should fail when newPassword is missing', async () => { + const response = await changePasswordReq( + { currentPassword: MEET_ENV.INITIAL_ADMIN_PASSWORD } as { + currentPassword: string; + newPassword: string; + }, + rootAdminAccessToken + ); + expectValidationError(response, 'newPassword', 'Required'); }); }); }); diff --git a/meet-ce/backend/tests/integration/api/users/get-profile.test.ts b/meet-ce/backend/tests/integration/api/users/get-profile.test.ts index 7ca1c4bf..6064c641 100644 --- a/meet-ce/backend/tests/integration/api/users/get-profile.test.ts +++ b/meet-ce/backend/tests/integration/api/users/get-profile.test.ts @@ -1,22 +1,70 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; -import { getMe, loginAdminUser, startTestServer } from '../../../helpers/request-helpers.js'; +import { MeetUserRole } from '@openvidu-meet/typings'; +import { MEET_ENV } from '../../../../src/environment.js'; +import { deleteAllUsers, getMe, loginRootAdmin, startTestServer } from '../../../helpers/request-helpers.js'; +import { setupTestUsers } from '../../../helpers/test-scenarios.js'; +import { TestUsers } from '../../../interfaces/scenarios.js'; describe('Users API Tests', () => { - let adminAccessToken: string; + let rootAdminAccessToken: string; + let testUsers: TestUsers; beforeAll(async () => { await startTestServer(); - adminAccessToken = await loginAdminUser(); + ({ accessToken: rootAdminAccessToken } = await loginRootAdmin()); + testUsers = await setupTestUsers(); + }); + + afterAll(async () => { + await deleteAllUsers(); }); describe('Profile Tests', () => { - it('should return 200 and admin profile', async () => { - const response = await getMe(adminAccessToken); + it('should return root admin profile', async () => { + const response = await getMe(rootAdminAccessToken); expect(response.status).toBe(200); - expect(response.body).toHaveProperty('userId', 'admin'); + expect(response.body).toHaveProperty('userId', MEET_ENV.INITIAL_ADMIN_USER); expect(response.body).toHaveProperty('name', 'Admin'); - expect(response.body).toHaveProperty('role', 'admin'); + expect(response.body).toHaveProperty('role', MeetUserRole.ADMIN); expect(response.body).toHaveProperty('registrationDate'); + expect(response.body).not.toHaveProperty('passwordHash'); + expect(response.body).not.toHaveProperty('mustChangePassword'); + }); + + it('should return ADMIN user profile', async () => { + const user = testUsers.admin; + const response = await getMe(user.accessToken); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('userId', user.user.userId); + expect(response.body).toHaveProperty('name', user.user.name); + expect(response.body).toHaveProperty('role', user.user.role); + expect(response.body).toHaveProperty('registrationDate', user.user.registrationDate); + expect(response.body).not.toHaveProperty('passwordHash'); + expect(response.body).not.toHaveProperty('mustChangePassword'); + }); + + it('should return USER user profile', async () => { + const user = testUsers.user; + const response = await getMe(user.accessToken); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('userId', user.user.userId); + expect(response.body).toHaveProperty('name', user.user.name); + expect(response.body).toHaveProperty('role', user.user.role); + expect(response.body).toHaveProperty('registrationDate', user.user.registrationDate); + expect(response.body).not.toHaveProperty('passwordHash'); + expect(response.body).not.toHaveProperty('mustChangePassword'); + }); + + it('should return ROOM_MEMBER user profile', async () => { + const user = testUsers.roomMember; + const response = await getMe(user.accessToken); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('userId', user.user.userId); + expect(response.body).toHaveProperty('name', user.user.name); + expect(response.body).toHaveProperty('role', user.user.role); + expect(response.body).toHaveProperty('registrationDate', user.user.registrationDate); + expect(response.body).not.toHaveProperty('passwordHash'); + expect(response.body).not.toHaveProperty('mustChangePassword'); }); }); });