diff --git a/constants.js b/constants.js index 89435573..434c7442 100644 --- a/constants.js +++ b/constants.js @@ -67,7 +67,8 @@ const CONSTANTS = { VOTED_USERS_API_ALLOWED_VOTE_TYPES: ["Up", "Down"], MAX_CATEGORY_MEMBERS_PER_API_CALL: 10, POLL_OPTIONS: ["One", "Two", "Three", "Four", "Five", "Six"], - MAX_POLL_OPTION_VOTED_USERS_TO_SEND_PER_API_CALL: 10 + MAX_POLL_OPTION_VOTED_USERS_TO_SEND_PER_API_CALL: 10, + ALLOWED_PROFILE_DETAIL_KEY_EDITS: ["name", "displayName", "bio"] //Used for temp/editprofiledetails - This specifies the properties that are allowed in the profiledetails object. All other properties will be deleted. } module.exports = CONSTANTS \ No newline at end of file diff --git a/controllers/Temp.js b/controllers/Temp.js index 8e76aaaf..24bacf96 100644 --- a/controllers/Temp.js +++ b/controllers/Temp.js @@ -130,47 +130,6 @@ class TempController { }) } - static #changedisplayname = (userId, desiredDisplayName) => { - return new Promise(resolve => { - if (typeof userId !== 'string') { - return resolve(HTTPWTHandler.badInput(`userId must be a string. Provided type: ${typeof userId}`)) - } - - if (!mongoose.isObjectIdOrHexString(userId)) { - return resolve(HTTPWTHandler.badInput('userId must be an objectId.')) - } - - if (typeof desiredDisplayName !== 'string') { - return resolve(HTTPWTHandler.badInput(`desiredDisplayName must be a string. Provided type: ${typeof desiredDisplayName}`)) - } - - desiredDisplayName = desiredDisplayName.trim(); - - if (desiredDisplayName.length > CONSTANTS.MAX_USER_DISPLAY_NAME_LENGTH) { - return resolve(HTTPWTHandler.badInput('Desired display name must be 20 characters or less.')) - } - - if (!CONSTANTS.VALID_DISPLAY_NAME_TEST.test(desiredDisplayName)) { - return resolve(HTTPWTHandler.badInput('Display name must only contain characters in the alphabet and must be a single line.')) - } - - // Check if user exist - User.findOne({ _id: {$eq: userId} }).lean().then(userFound => { - if (!userFound) return resolve(HTTPWTHandler.notFound('Could not find user with provided userId.')) - - User.findOneAndUpdate({_id: {$eq: userId}}, {displayName: String(desiredDisplayName)}).then(function() { - return resolve(HTTPWTHandler.OK('Display name changed successfully.')) - }).catch(err => { - console.error('An error occurred while changing the display name of user with id:', userId, 'to:', desiredDisplayName, '. The error was:', err) - return resolve(HTTPWTHandler.serverError('An error occurred while updating display name. Please try again.')) - }) - }).catch(err => { - console.error('An error occurred while finding one user with id:', userId, '. The error was:', err) - return resolve(HTTPWTHandler.serverError('An error occurred while finding existing user. Plesae try again.')) - }) - }) - } - static #changeemail = (userId, password, desiredEmail) => { return new Promise(resolve => { if (typeof userId !== 'string') { @@ -386,99 +345,6 @@ class TempController { }) } - static #changeusername = (userId, desiredUsername) => { - return new Promise(resolve => { - if (typeof userId !== 'string') { - return resolve(HTTPWTHandler.badInput(`userId must be a string. Provided type: ${typeof userId}`)) - } - - if (!mongoose.isObjectIdOrHexString(userId)) { - return resolve(HTTPWTHandler.badInput('userId must be an ObjectId.')) - } - - if (typeof desiredUsername !== 'string') { - return resolve(HTTPWTHandler.badInput(`desiredUsername must be a string. Provided type: ${typeof desiredUsername}`)) - } - - if (!CONSTANTS.VALID_USERNAME_TEST.test(desiredUsername)) { - return resolve(HTTPWTHandler.badInput('Invalid username entered (username can only have numbers and lowercase a - z characters)')) - } - - desiredUsername = desiredUsername.trim(); - - if (desiredUsername.length === 0) { - return resolve(HTTPWTHandler.badInput('Desired username cannot be blank.')) - } - - if (desiredUsername.length > CONSTANTS.MAX_USER_USERNAME_LENGTH) { - return resolve(HTTPWTHandler.badInput('Your new username cannot be more than 20 characters.')) - } - - User.findOne({_id: {$eq: userId}}).lean().then(userFound => { - if (!userFound) return resolve(HTTPWTHandler.notFound('Could not find user with provided userId.')); - - User.findOne({name: {$eq: desiredUsername}}).lean().then(result => { - if (result) return resolve(HTTPWTHandler.conflict('User with the provided username already exists')) - - User.findOneAndUpdate({_id: {$eq: userId}}, {name: String(desiredUsername)}).then(() => { - return resolve(HTTPWTHandler.OK('Change Username Successful')) - }).catch(err => { - console.error('An error occured while updating user with id:', userId, ' to have a username:', desiredUsername, '. The error was:', err) - return resolve(HTTPWTHandler.serverError('An error occurred while updating your username. Please try again.')) - }); - }).catch(error => { - console.error('An error occured while finding one user with name:', desiredUsername, '. The error was:', error) - return resolve(HTTPWTHandler.serverError('An error occurred while checking for existing user. Please try again.')) - }) - }).catch(err => { - console.error('An error occured while checking for a user with id:', userId, '. The error was:', err) - return resolve(HTTPWTHandler.serverError('An error occurred while finding user. Please try again.')) - }) - }) - } - - static #changebio = (userId, bio) => { - return new Promise(resolve => { - if (typeof userId !== 'string') { - return resolve(HTTPWTHandler.badInput(`userId must be a string. Type provided: ${typeof userId}`)) - } - - if (!mongoose.isObjectIdOrHexString(userId)) { - return resolve(HTTPWTHandler.badInput(`userId must be an ObjectId.`)) - } - - if (typeof bio !== 'string') { - return resolve(HTTPWTHandler.badInput(`bio must be a string. Provided type: ${typeof bio}`)) - } - - if (bio.length > CONSTANTS.MAX_USER_BIO_LENGTH) { - return resolve(HTTPWTHandler.badInput(`Bio must be ${CONSTANTS.MAX_USER_BIO_LENGTH} or less characters`)) - } - - if (!CONSTANTS.VALID_BIO_TEST.test(bio)) { - return resolve(HTTPWTHandler.badInput(`Bio must have ${CONSTANTS.MAX_USER_BIO_LINES} or less lines`)) - } - - User.findOne({_id: {$eq: userId}}).lean().then((data) => { - if (data) { - User.findOneAndUpdate({_id: {$eq: userId}}, {$set: {bio: String(bio)}}).then(function(){ - return resolve(HTTPWTHandler.OK('Change Successful')) - }) - .catch(err => { - console.error('An error occured while updating user with id:', userId, ' bio to:', bio, '. The error was:', err) - return resolve(HTTPWTHandler.serverError('An error occurred while updating bio. Please try again.')) - }); - } else { - return resolve(HTTPWTHandler.notFound('User with provided userId could not be found')) - } - }) - .catch(err => { - console.error('An error occured while finding user with id:', userId, '. The error was:', err) - return resolve(HTTPWTHandler.serverError('An error occurred while checking for existing user. Please try again.')) - }) - }) - } - static #searchpageusersearch = (userId, skip, val) => { return new Promise(resolve => { if (typeof userId !== 'string') { @@ -6258,12 +6124,93 @@ class TempController { }) } - static sendnotificationkey = async (userId, notificationKey, refreshTokenId) => { - return await this.#sendnotificationkey(userId, notificationKey, refreshTokenId) + static #editprofiledetails = (userId, profileDetails) => { + return new Promise(resolve => { + if (typeof userId !== 'string') { + return resolve(HTTPWTHandler.badInput(`userId must be a string. Provided type: ${typeof userId}`)) + } + + if (!mongoose.isObjectIdOrHexString(userId)) { + return resolve(HTTPWTHandler.badInput('userId must be an ObjectId.')) + } + + if (typeof profileDetails !== 'object' || Array.isArray(profileDetails) || profileDetails === null) { + return resolve(HTTPWTHandler.badInput('profileDetails must be an object.')) + } + + for (const key of Object.keys(profileDetails)) { + if (!CONSTANTS.ALLOWED_PROFILE_DETAIL_KEY_EDITS.includes(key)) { + delete profileDetails[key] + continue + } + + if (typeof profileDetails[key] !== 'string') { + return resolve(HTTPWTHandler.badInput(`${key} must be a string.`)) + } + } + + if (Object.keys(profileDetails).length === 0) { + return resolve(HTTPWTHandler.badInput('No valid keys were provided for profileDetails.')) + } + + if (profileDetails.name) { + if (!CONSTANTS.VALID_USERNAME_TEST.test(profileDetails.name)) { + return resolve(HTTPWTHandler.badInput('Invalid username entered (username can only have numbers and lowercase a - z characters)')) + } + + if (profileDetails.name.length === 0) { + return resolve(HTTPWTHandler.badInput('Your username cannot be blank.')) + } + + if (profileDetails.name.length > CONSTANTS.MAX_USER_USERNAME_LENGTH) { + return resolve(HTTPWTHandler.badInput('Your new username cannot be more than 20 characters.')) + } + } + + if (profileDetails.displayName) { + if (profileDetails.displayName.length > CONSTANTS.MAX_USER_DISPLAY_NAME_LENGTH) { + return resolve(HTTPWTHandler.badInput('Display name must be 20 characters or less.')) + } + + if (!CONSTANTS.VALID_DISPLAY_NAME_TEST.test(profileDetails.displayName)) { + return resolve(HTTPWTHandler.badInput('Display name must only contain a-z lowercase and uppercase characters.')) + } + } + + if (profileDetails.bio) { + if (profileDetails.bio.length > CONSTANTS.MAX_USER_BIO_LENGTH) { + return resolve(HTTPWTHandler.badInput(`Bio must be ${CONSTANTS.MAX_USER_BIO_LENGTH} or less characters`)) + } + + if (!CONSTANTS.VALID_BIO_TEST.test(profileDetails.bio)) { + return resolve(HTTPWTHandler.badInput(`Bio must have ${CONSTANTS.MAX_USER_BIO_LINES} or less lines`)) + } + } + + User.findOneAndUpdate({_id: {$eq: userId}}, {$set: profileDetails}).lean().then(result => { + if (result === null) { + return resolve(HTTPWTHandler.notFound('Could not find user with provided userId.')); + } + + return resolve(HTTPWTHandler.OK('Profile details were successfully edited')) + }).catch(error => { + if (error.codeName === 'DuplicateKey') { + if (Object.keys(error.keyValue)[0] === 'name') { + return resolve(HTTPWTHandler.conflict('Another SocialSquare user has the username you are trying to use. Please choose a different username.')) + } else { + console.error('An unknown key is giving a duplicate error while updating profile details. The error is:', error) + return resolve(HTTPWTHandler.serverError('An unknown key cannot be duplicated.')) + } + } else { + console.error('An error occurred while updating user profiles. The user detail object was:', profileDetails, '. The error was:', error) + return resolve(HTTPWTHandler.serverError('An error occurred while updating profile details. Please try again.')) + } + }) + }) } - static changedisplayname = async (userId, desiredDisplayName) => { - return await this.#changedisplayname(userId, desiredDisplayName) + static sendnotificationkey = async (userId, notificationKey, refreshTokenId) => { + return await this.#sendnotificationkey(userId, notificationKey, refreshTokenId) } static changeemail = async (userId, password, desiredEmail) => { @@ -6274,14 +6221,6 @@ class TempController { return await this.#changepassword(userId, currentPassword, newPassword, confirmNewPassword, IP, deviceType) } - static changeusername = async (userId, desiredUsername) => { - return await this.#changeusername(userId, desiredUsername) - } - - static changebio = async (userId, bio) => { - return await this.#changebio(userId, bio) - } - static searchpageusersearch = async (userId, skip, val) => { return await this.#searchpageusersearch(userId, skip, val) } @@ -6581,6 +6520,10 @@ class TempController { static getpollvoteusers = async (userId, pollId, pollOption, lastItemId) => { return await this.#getpollvoteusers(userId, pollId, pollOption, lastItemId) } + + static editprofiledetails = async (userId, profileDetails) => { + return await this.#editprofiledetails(userId, profileDetails) + } } module.exports = TempController; diff --git a/routes/Temp.js b/routes/Temp.js index 06fbf860..853088a5 100644 --- a/routes/Temp.js +++ b/routes/Temp.js @@ -45,15 +45,6 @@ const rateLimiters = { skipFailedRequests: true, // Request will not be counted if it fails keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit }), - '/changedisplayname': rateLimit({ - windowMs: 1000 * 60 * 60 * 24, //1 day - max: 3, - standardHeaders: false, - legacyHeaders: false, - message: {status: "FAILED", message: "The display name for this account has changed too many times today. Please try again in 24 hours."}, - skipFailedRequests: true, - keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit - }), '/changeemail': rateLimit({ windowMs: 1000 * 60 * 60 * 24, //1 day max: 2, @@ -72,24 +63,6 @@ const rateLimiters = { skipFailedRequests: true, keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit }), - '/changeusername': rateLimit({ - windowMs: 1000 * 60 * 60 * 24, //1 day - max: 3, - standardHeaders: false, - legacyHeaders: false, - message: {status: "FAILED", message: "The username for this account has been changed more too many times today. Please try again in 24 hours."}, - skipFailedRequests: true, - keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit - }), - '/changebio': rateLimit({ - windowMs: 1000 * 60 * 60 * 24, //1 day - max: 20, - standardHeaders: false, - legacyHeaders: false, - message: {status: "FAILED", message: "The bio for this account has been changed too many times today. Please try again in 24 hours."}, - skipFailedRequests: true, - keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit - }), '/searchpageusersearch': rateLimit({ windowMs: 1000 * 60, //1 minute max: 60, @@ -794,6 +767,15 @@ const rateLimiters = { skipFailedRequests: true, keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit }), + '/editprofiledetails': rateLimit({ + windowMs: 1000 * 60 * 60 * 6, //6 hours + max: 10, + standardHeaders: false, + legacyHeaders: false, + message: {status: "FAILED", message: "The details for this account has been changed too many times in the past 6 hours. Please try again in 6 hours."}, + skipFailedRequests: true, + keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit + }), } @@ -823,32 +805,6 @@ router.post('/sendnotificationkey', rateLimiters['/sendnotificationkey'], (req, }) }); -router.post('/changedisplayname', rateLimiters['/changedisplayname'], (req, res) => { - const worker = new Worker(workerPath, { - workerData: { - functionName: 'changedisplayname', - functionArgs: [req.tokenData, req.body.desiredDisplayName] - } - }) - - worker.on('message', (result) => { - if (!res.headersSent) { - res.status(result.statusCode).json(result.data) - } else { - console.error('POST temp/changedisplayname controller function returned data to be sent to the client but HTTP headers have already been sent! Data attempted to send:', result) - } - }) - - worker.on('error', (error) => { - if (!res.headersSent) { - console.error('An error occurred from TempWorker for POST /changedisplayname:', error) - HTTPHandler.serverError(res, String(error)) - } else { - console.error('POST temp/changedisplayname controller function encountered an error and tried to send it to the client but HTTP headers have already been sent! Error attempted to send:', error) - } - }) -}); - router.post('/changeemail', rateLimiters['/changeemail'], (req, res) => { const worker = new Worker(workerPath, { workerData: { @@ -901,58 +857,6 @@ router.post('/changepassword', rateLimiters['/changepassword'], HTTPHandler.getD }) }); -router.post('/changeusername', rateLimiters['/changeusername'], (req, res) => { - const worker = new Worker(workerPath, { - workerData: { - functionName: 'changeusername', - functionArgs: [req.tokenData, req.body.desiredUsername] - } - }) - - worker.on('message', (result) => { - if (!res.headersSent) { - res.status(result.statusCode).json(result.data) - } else { - console.error('POST temp/changeusername controller function returned data to be sent to the client but HTTP headers have already been sent! Data attempted to send:', result) - } - }) - - worker.on('error', (error) => { - if (!res.headersSent) { - console.error('An error occurred from TempWorker for POST /changeusername:', error) - HTTPHandler.serverError(res, String(error)) - } else { - console.error('POST temp/changeusername controller function encountered an error and tried to send it to the client but HTTP headers have already been sent! Error attempted to send:', error) - } - }) -}); - -router.post('/changebio', rateLimiters['/changebio'], (req, res) => { - const worker = new Worker(workerPath, { - workerData: { - functionName: 'changebio', - functionArgs: [req.tokenData, req.body.bio] - } - }) - - worker.on('message', (result) => { - if (!res.headersSent) { - res.status(result.statusCode).json(result.data) - } else { - console.error('POST temp/changebio controller function returned data to be sent to the client but HTTP headers have already been sent! Data attempted to send:', result) - } - }) - - worker.on('error', (error) => { - if (!res.headersSent) { - console.error('An error occurred from TempWorker for POST /changebio:', error) - HTTPHandler.serverError(res, String(error)) - } else { - console.error('POST temp/changebio controller function encountered an error and tried to send it to the client but HTTP headers have already been sent! Error attempted to send:', error) - } - }) -}); - router.post('/searchpageusersearch', rateLimiters['/searchpageusersearch'], (req, res) => { const worker = new Worker(workerPath, { workerData: { @@ -3013,4 +2917,30 @@ router.post('/getpollvoteusers', rateLimiters['/getpollvoteusers'], (req, res) = }) }); +router.post('/editprofiledetails', rateLimiters['/editprofiledetails'], (req, res) => { + const worker = new Worker(workerPath, { + workerData: { + functionName: 'editprofiledetails', + functionArgs: [req.tokenData, req.body] + } + }) + + worker.on('message', (result) => { + if (!res.headersSent) { + res.status(result.statusCode).json(result.data) + } else { + console.error('POST temp/editprofiledetails controller function returned data to be sent to the client but HTTP headers have already been sent! Data attempted to send:', result) + } + }) + + worker.on('error', (error) => { + if (!res.headersSent) { + console.error('An error occurred from TempWorker for POST /editprofiledetails:', error) + HTTPHandler.serverError(res, String(error)) + } else { + console.error('POST temp/editprofiledetails controller function encountered an error and tried to send it to the client but HTTP headers have already been sent! Error attempted to send:', error) + } + }) +}); + module.exports = router; \ No newline at end of file diff --git a/tests/temp/changebio.test.js b/tests/temp/changebio.test.js deleted file mode 100644 index d4103125..00000000 --- a/tests/temp/changebio.test.js +++ /dev/null @@ -1,166 +0,0 @@ -const User = require('../../models/User'); -const MockMongoDBServer = require('../../libraries/MockDBServer'); -const TEST_CONSTANTS = require('../TEST_CONSTANTS'); -const TempController = require('../../controllers/Temp') - -const {beforeAll, afterEach, afterAll, test, expect} = require('@jest/globals'); - -const DB = new MockMongoDBServer(); - -beforeAll(async () => { - await DB.startTest(); -}) - -afterEach(async () => { - await DB.purgeData() -}) - -afterAll(async () => { - await DB.stopTest() -}) - -const validUser = { - _id: '655e091738d16dd48778d1df', - bio: 'Old bio' -} - -const VALID_BIO = "my bio here"; - -/* -Tests: -- Test if change fails if userId is not a string -- Test if change fails if userId is not an ObjectId -- Test if change fails if bio is not a string -- Test if change fails if bio is longer than 250 characters -- Test if change fails if bio has more than 4 lines -- Test if change fails if user could not be found -- Test if change is successful with correct inputs -- Test if change does not interfere with other User documents -*/ - -for (const notString of TEST_CONSTANTS.NOT_STRINGS) { - test(`If change fails if userId is not a string. Testing: ${JSON.stringify(notString)}`, async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changebio(notString, VALID_BIO); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe(`userId must be a string. Type provided: ${typeof notString}`) - expect(await DB.noChangesMade()).toBe(true) - }) - - test(`If change fails if bio is not a string. Testing: ${JSON.stringify(notString)}`, async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changebio(validUser._id, notString); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe(`bio must be a string. Provided type: ${typeof notString}`) - expect(await DB.noChangesMade()).toBe(true) - }) -} - -test('If change fails if userId is not an ObjectId', async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changebio('this is not an objectid', VALID_BIO); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe('userId must be an ObjectId.') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change fails if bio is longer than 250 characters', async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changebio(validUser._id, new Array(252).join('a')); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe('Bio must be 250 or less characters') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change fails if bio has more than 4 lines', async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changebio(validUser._id, ` - - this is more than 4 lines here - - `); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe('Bio must have 4 or less lines') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change fails if user could not be found', async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changebio(validUser._id, VALID_BIO); - - expect(returned.statusCode).toBe(404); - expect(returned.data.message).toBe('User with provided userId could not be found') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change is successful with correct inputs', async () => { - expect.assertions(4); - - await new User(validUser).save(); - - await DB.takeDBSnapshot() - - const beforeUser = await User.findOne({_id: validUser._id}).lean(); - - const returned = await TempController.changebio(validUser._id, VALID_BIO); - - const afterUser = await User.findOne({_id: validUser._id}).lean(); - - beforeUser.bio = VALID_BIO; - - expect(returned.statusCode).toBe(200); - expect(returned.data.message).toBe('Change Successful'); - expect(beforeUser).toStrictEqual(afterUser) - expect(await DB.changedCollections()).toIncludeSameMembers(['User']) -}) - -test('If change does not interfere with other User documents', async () => { - expect.assertions(4); - - const newUsers = [...new Array(10)].map((item, index) => { - return { - bio: `Bio: ${index}`, - name: `name${index}` - } - }) - - await User.insertMany(newUsers) - - await DB.takeDBSnapshot() - - const beforeUsers = await User.find({}).lean(); - - await new User(validUser).save(); - - const returned = await TempController.changebio(validUser._id, VALID_BIO); - - const afterUsers = await User.find({_id: {$ne: validUser._id}}).lean(); - - expect(returned.statusCode).toBe(200); - expect(returned.data.message).toBe('Change Successful'); - expect(beforeUsers).toStrictEqual(afterUsers); - expect(await DB.changedCollections()).toIncludeSameMembers(['User']) -}) \ No newline at end of file diff --git a/tests/temp/changedisplayname.test.js b/tests/temp/changedisplayname.test.js deleted file mode 100644 index 5813b2ad..00000000 --- a/tests/temp/changedisplayname.test.js +++ /dev/null @@ -1,192 +0,0 @@ -const MockMongoDBServer = require('../../libraries/MockDBServer'); -const User = require('../../models/User'); -const TEST_CONSTANTS = require('../TEST_CONSTANTS'); -const TempController = require('../../controllers/Temp'); -const mongoose = require('mongoose'); - -const DB = new MockMongoDBServer(); - -const {beforeAll, afterEach, afterAll, test, expect} = require('@jest/globals'); - -jest.setTimeout(20_000) //20s per test - -beforeAll(async () => { - await DB.startTest() -}) - -afterEach(async () => { - await DB.purgeData(); -}) - -afterAll(async () => { - await DB.stopTest() -}) - -/* -Tests: -Test if change fails if userId is not a string -Test if change fails if userId is not an objectId -Test if change fails if desiredDisplayName is not a string -Test if change fails if desiredDisplayName is more than 20 characters -Test if change fails if user could not be found -Test if change successfully changes display name and only changes display name -Test if change does not interfere with already existing user documents -Test if change fails if desiredDisplayName is multi-line -Test if change fails if desiredDisplayName has non-alphabetic characters -*/ - -const userData = { - name: 'sebastian', - displayName: 'Sebastian', - _id: new mongoose.Types.ObjectId() -} - -for (const notString of TEST_CONSTANTS.NOT_STRINGS) { - test(`If change fails if userId is not a string. Testing: ${JSON.stringify(notString)}`, async () => { - expect.assertions(3); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const returned = await TempController.changedisplayname(notString, 'newname'); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe(`userId must be a string. Provided type: ${typeof notString}`); - expect(await DB.noChangesMade()).toBe(true) - }) - - test(`If change fails if desiredDisplayName is not a string. Testing: ${JSON.stringify(notString)}`, async () => { - expect.assertions(3); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const returned = await TempController.changedisplayname(String(userData._id), notString) - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe(`desiredDisplayName must be a string. Provided type: ${typeof notString}`) - expect(await DB.noChangesMade()).toBe(true) - }) -} - -test('If change fails if userId is not an objectId', async () => { - expect.assertions(3); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const returned = await TempController.changedisplayname('i am not an objectid', 'newname'); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe('userId must be an objectId.') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change fails if desiredDisplayName is longer than 20 characters', async () => { - expect.assertions(3); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const returned = await TempController.changedisplayname(String(userData._id), "this is 21 characters"); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe('Desired display name must be 20 characters or less.') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change fails if user could not be found', async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changedisplayname(String(new mongoose.Types.ObjectId()), 'helloworld'); - - expect(returned.statusCode).toBe(404); - expect(returned.data.message).toBe('Could not find user with provided userId.') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change successfully changes display name and only display name', async () => { - expect.assertions(4); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const beforeUser = await User.findOne({}).lean(); - - const returned = await TempController.changedisplayname(String(userData._id), 'newdisplayname'); - - const afterUser = await User.findOne({}).lean(); - - const beforeDisplayName = beforeUser.displayName; - const afterDisplayName = afterUser.displayName; - - delete beforeUser.displayName; - delete afterUser.displayName; - - expect(returned.statusCode).toBe(200); - expect(beforeUser).toStrictEqual(afterUser); - expect(beforeDisplayName !== afterDisplayName).toBe(true); - expect(await DB.changedCollections()).toIncludeSameMembers(['User']) -}) - -test('If change does not modify already existing User documents', async () => { - expect.assertions(3); - - const usersToInsert = [...new Array(10)].map((item, index) => { - return { - name: 'sebastian' + index, - displayName: 'Sebastian' + index - } - }) - - await User.insertMany(usersToInsert); - - const beforeUsers = await User.find({}).lean(); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const returned = await TempController.changedisplayname(String(userData._id), 'newdisplayname'); - - const afterUsers = await User.find({_id: {$ne: userData._id}}).lean(); - - expect(returned.statusCode).toBe(200); - expect(beforeUsers).toStrictEqual(afterUsers); - expect(await DB.changedCollections()).toIncludeSameMembers(['User']) -}) - -test('If change fails if desiredDisplayName is multi-line', async () => { - expect.assertions(3); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const returned = await TempController.changedisplayname(String(userData._id), `new\ndisplay\nname`); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe("Display name must only contain characters in the alphabet and must be a single line.") - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change fails if desiredDisplayName contains non-alphabetic characters', async () => { - expect.assertions(3); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const returned = await TempController.changedisplayname(String(userData._id), '*&^%$#%^&*()') - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe("Display name must only contain characters in the alphabet and must be a single line.") - expect(await DB.noChangesMade()).toBe(true) -}) \ No newline at end of file diff --git a/tests/temp/changeusername.test.js b/tests/temp/changeusername.test.js deleted file mode 100644 index 335d6a5b..00000000 --- a/tests/temp/changeusername.test.js +++ /dev/null @@ -1,201 +0,0 @@ -const mongoose = require('mongoose'); -const {v4: uuidv4} = require('uuid'); - -const User = require('../../models/User'); -const MockMongoDBServer = require('../../libraries/MockDBServer'); -const TEST_CONSTANTS = require('../TEST_CONSTANTS'); -const TempController = require('../../controllers/Temp'); - -const {expect, afterEach, beforeAll, afterAll} = require('@jest/globals'); - -const DB = new MockMongoDBServer(); - -beforeAll(async () => { - await DB.startTest(); -}) - -afterEach(async () => { - await DB.purgeData() -}) - -afterAll(async () => { - await DB.stopTest() -}) - -const validUsername = 'sebastian'; - -const invalidUsernames = ["$", "$%^&*(", "seb_1", "hello!", `hi\n`, "~~~", 'seb.seb'] - -const userData = { - _id: new mongoose.Types.ObjectId(), - secondId: uuidv4(), - name: 'myname', - displayName: 'Sebastian' -} - -/* -Tests: -- Test that change fails if userId is not a string -- Test that change fails if userId is not an objectId -- Test that change fails if desiredUsername is not a string -- Test that change fails if desiredUsername is -- Test that change fails if desiredUsername does not pass the valid username test -- Test that change fails if desiredUsername is more than 20 characters (CONSTANTS.MAX_USER_USERNAME_LENGTH) -- Test that change fails if user with userId could not be found -- Test that change fails if user with current desiredUsername could be found -- Test that change is successful with correct inputs -- Test that successful change does not interfere with already existing User documents in the database -*/ - -for (const notString of TEST_CONSTANTS.NOT_STRINGS) { - test(`If change fails if userId is not a string. Testing: ${JSON.stringify(notString)}`, async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changeusername(notString, validUsername); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe(`userId must be a string. Provided type: ${typeof notString}`) - expect(await DB.noChangesMade()).toBe(true) - }) - - test(`If change fails if desiredUsername is not a string. Testing: ${JSON.stringify(notString)}`, async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changeusername(String(userData._id), notString); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe(`desiredUsername must be a string. Provided type: ${typeof notString}`) - expect(await DB.noChangesMade()).toBe(true) - }) -} - -test('if change fails if userId is not an ObjectId', async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changeusername('i am not an Objectid', validUsername); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe('userId must be an ObjectId.'); - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change fails if desiredUsername is blank', async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changeusername(String(userData._id), ''); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe('Desired username cannot be blank.') - expect(await DB.noChangesMade()).toBe(true) -}) - -for (const invalidUsername of invalidUsernames) { - test(`If change fails if desiredUsername does not pass the valid username test. Testing: ${invalidUsername}`, async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changeusername(String(userData._id), invalidUsername); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe('Invalid username entered (username can only have numbers and lowercase a - z characters)') - expect(await DB.noChangesMade()).toBe(true) - }) -} - -test('If change fails if desiredUsername is more than 20 characters', async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changeusername(String(userData._id), 'thisis21characterssss'); - - expect(returned.statusCode).toBe(400); - expect(returned.data.message).toBe('Your new username cannot be more than 20 characters.') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change fails if user with userId could not be found', async () => { - expect.assertions(3); - - await DB.takeDBSnapshot() - - const returned = await TempController.changeusername(String(userData._id), 'newname'); - - expect(returned.statusCode).toBe(404); - expect(returned.data.message).toBe('Could not find user with provided userId.') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change fails when user with desiredUsername already exists', async () => { - expect.assertions(3); - - await new User(userData).save(); - await new User({ - name: 'newname' - }).save(); - - await DB.takeDBSnapshot() - - const returned = await TempController.changeusername(String(userData._id), 'newname'); - - expect(returned.statusCode).toBe(409); - expect(returned.data.message).toBe('User with the provided username already exists') - expect(await DB.noChangesMade()).toBe(true) -}) - -test('If change is successful with correct inputs', async () => { - expect.assertions(3); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const beforeUser = await User.findOne({}).lean(); - - const returned = await TempController.changeusername(String(userData._id), 'newname'); - - const afterUser = await User.findOne({}).lean(); - - beforeUser.name = 'newname' - - expect(returned.statusCode).toBe(200); - expect(afterUser).toStrictEqual(beforeUser) - expect(await DB.changedCollections()).toIncludeSameMembers(['User']) -}) - -test('that successful change of username does not interfere with other User documents', async () => { - expect.assertions(3); - - const users = [...new Array(10)].map((item, index) => { - return { - _id: new mongoose.Types.ObjectId(), - secondId: uuidv4(), - name: 'name' + index - } - }) - - await User.insertMany(users); - - const beforeUsers = await User.find({}).lean(); - - await new User(userData).save(); - - await DB.takeDBSnapshot() - - const returned = await TempController.changeusername(String(userData._id), 'newname'); - - const afterUsers = await User.find({_id: {$ne: userData._id}}).lean(); - - expect(returned.statusCode).toBe(200); - expect(beforeUsers).toStrictEqual(afterUsers); - expect(await DB.changedCollections()).toIncludeSameMembers(['User']) -}) \ No newline at end of file