diff --git a/services/real-time/test/unit/js/ConnectedUsersManagerTests.js b/services/real-time/test/unit/js/ConnectedUsersManagerTests.js index dd4aeb35c9..a6864075e0 100644 --- a/services/real-time/test/unit/js/ConnectedUsersManagerTests.js +++ b/services/real-time/test/unit/js/ConnectedUsersManagerTests.js @@ -20,6 +20,7 @@ const tk = require('timekeeper') describe('ConnectedUsersManager', function () { beforeEach(function () { + tk.freeze(new Date()) this.settings = { redis: { realtime: { @@ -56,7 +57,6 @@ describe('ConnectedUsersManager', function () { return this.rClient }, } - tk.freeze(new Date()) this.Metrics = { inc: sinon.stub(), histogram: sinon.stub(), diff --git a/services/web/app/src/Features/Editor/EditorController.js b/services/web/app/src/Features/Editor/EditorController.js index ea0153fe37..4d3a5e9eb0 100644 --- a/services/web/app/src/Features/Editor/EditorController.js +++ b/services/web/app/src/Features/Editor/EditorController.js @@ -307,6 +307,7 @@ const EditorController = { projectId, folderId, folderName, + userId, (err, folder, folderId) => { if (err) { OError.tag(err, 'could not add folder', { @@ -333,11 +334,12 @@ const EditorController = { ) }, - mkdirp(projectId, path, callback) { + mkdirp(projectId, path, userId, callback) { logger.debug({ projectId, path }, "making directories if they don't exist") ProjectEntityUpdateHandler.mkdirp( projectId, path, + userId, (err, newFolders, lastFolder) => { if (err) { OError.tag(err, 'could not mkdirp', { diff --git a/services/web/app/src/Features/History/RestoreManager.js b/services/web/app/src/Features/History/RestoreManager.js index f36f1466af..8c73695eed 100644 --- a/services/web/app/src/Features/History/RestoreManager.js +++ b/services/web/app/src/Features/History/RestoreManager.js @@ -33,7 +33,8 @@ const RestoreManager = { } const parentFolderId = await RestoreManager._findOrCreateFolder( projectId, - dirname + dirname, + userId ) const addEntityWithName = async name => await FileSystemImportManager.promises.addEntity( @@ -71,7 +72,8 @@ const RestoreManager = { } const parentFolderId = await RestoreManager._findOrCreateFolder( projectId, - dirname + dirname, + userId ) const file = await ProjectLocator.promises .findElementByPath({ @@ -264,10 +266,11 @@ const RestoreManager = { } }, - async _findOrCreateFolder(projectId, dirname) { + async _findOrCreateFolder(projectId, dirname, userId) { const { lastFolder } = await EditorController.promises.mkdirp( projectId, - dirname + dirname, + userId ) return lastFolder?._id }, diff --git a/services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js b/services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js index 31b3efaec8..84002f1a38 100644 --- a/services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js +++ b/services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js @@ -105,7 +105,7 @@ function wrapWithLock(methodWithoutLock) { return methodWithLock } -async function addDoc(projectId, folderId, doc) { +async function addDoc(projectId, folderId, doc, userId) { const project = await ProjectGetter.promises.getProjectWithoutLock( projectId, { @@ -119,12 +119,13 @@ async function addDoc(projectId, folderId, doc) { project, folderId, doc, - 'doc' + 'doc', + userId ) return { result, project: newProject } } -async function addFile(projectId, folderId, fileRef) { +async function addFile(projectId, folderId, fileRef, userId) { const project = await ProjectGetter.promises.getProjectWithoutLock( projectId, { rootFolder: true, name: true, overleaf: true } @@ -134,23 +135,24 @@ async function addFile(projectId, folderId, fileRef) { project, folderId, fileRef, - 'file' + 'file', + userId ) return { result, project: newProject } } -async function addFolder(projectId, parentFolderId, folderName) { +async function addFolder(projectId, parentFolderId, folderName, userId) { const project = await ProjectGetter.promises.getProjectWithoutLock( projectId, { rootFolder: true, name: true, overleaf: true } ) parentFolderId = _confirmFolder(project, parentFolderId) const folder = new Folder({ name: folderName }) - await _putElement(project, parentFolderId, folder, 'folder') + await _putElement(project, parentFolderId, folder, 'folder', userId) return { folder, parentFolderId } } -async function replaceFileWithNew(projectId, fileId, newFileRef) { +async function replaceFileWithNew(projectId, fileId, newFileRef, userId) { const project = await ProjectGetter.promises.getProjectWithoutLock( projectId, { rootFolder: true, name: true, overleaf: true } @@ -169,6 +171,8 @@ async function replaceFileWithNew(projectId, fileId, newFileRef) { [`${path.mongo}.created`]: new Date(), [`${path.mongo}.linkedFileData`]: newFileRef.linkedFileData, [`${path.mongo}.hash`]: newFileRef.hash, + lastUpdated: new Date(), + lastUpdatedBy: userId, }, $inc: { version: 1, @@ -194,7 +198,7 @@ async function replaceFileWithNew(projectId, fileId, newFileRef) { return { oldFileRef: fileRef, project, path, newProject, newFileRef } } -async function replaceDocWithFile(projectId, docId, fileRef) { +async function replaceDocWithFile(projectId, docId, fileRef, userId) { const project = await ProjectGetter.promises.getProjectWithoutLock( projectId, { rootFolder: true, name: true, overleaf: true } @@ -215,6 +219,7 @@ async function replaceDocWithFile(projectId, docId, fileRef) { [`${folderMongoPath}.fileRefs`]: fileRef, }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, }, { new: true } ).exec() @@ -227,7 +232,7 @@ async function replaceDocWithFile(projectId, docId, fileRef) { return newProject } -async function replaceFileWithDoc(projectId, fileId, newDoc) { +async function replaceFileWithDoc(projectId, fileId, newDoc, userId) { const project = await ProjectGetter.promises.getProjectWithoutLock( projectId, { rootFolder: true, name: true, overleaf: true } @@ -248,6 +253,7 @@ async function replaceFileWithDoc(projectId, fileId, newDoc) { [`${folderMongoPath}.docs`]: newDoc, }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, }, { new: true } ).exec() @@ -260,7 +266,7 @@ async function replaceFileWithDoc(projectId, fileId, newDoc) { return newProject } -async function mkdirp(projectId, path, options = {}) { +async function mkdirp(projectId, path, userId, options = {}) { // defaults to case insensitive paths, use options {exactCaseMatch:true} // to make matching case-sensitive const folders = path.split('/').filter(folder => folder.length !== 0) @@ -289,7 +295,7 @@ async function mkdirp(projectId, path, options = {}) { // Folder couldn't be found. Create it. const parentFolderId = lastFolder && lastFolder._id const { folder: newFolder, parentFolderId: newParentFolderId } = - await addFolder(projectId, parentFolderId, folderName) + await addFolder(projectId, parentFolderId, folderName, userId) newFolder.parentFolder_id = newParentFolderId lastFolder = newFolder newFolders.push(newFolder) @@ -298,7 +304,13 @@ async function mkdirp(projectId, path, options = {}) { return { folder: lastFolder, newFolders } } -async function moveEntity(projectId, entityId, destFolderId, entityType) { +async function moveEntity( + projectId, + entityId, + destFolderId, + entityType, + userId +) { const project = await ProjectGetter.promises.getProjectWithoutLock( projectId, { rootFolder: true, name: true, overleaf: true } @@ -326,7 +338,8 @@ async function moveEntity(projectId, entityId, destFolderId, entityType) { project, destFolderId, entity, - entityType + entityType, + userId ) // Note: putElement always pushes onto the end of an // array so it will never change an existing mongo @@ -337,10 +350,10 @@ async function moveEntity(projectId, entityId, destFolderId, entityType) { // is done by _checkValidMove above) because that // would lead to it being deleted. const newProject = await _removeElementFromMongoArray( - Project, projectId, entityPath.mongo, - entityId + entityId, + userId ) const { docs: newDocs, files: newFiles } = ProjectEntityHandler.getAllEntitiesFromProject(newProject) @@ -375,7 +388,7 @@ async function moveEntity(projectId, entityId, destFolderId, entityType) { return { project, startPath, endPath, rev: entity.rev, changes } } -async function deleteEntity(projectId, entityId, entityType) { +async function deleteEntity(projectId, entityId, entityType, userId) { const project = await ProjectGetter.promises.getProjectWithoutLock( projectId, { name: true, rootFolder: true, overleaf: true, rootDoc_id: true } @@ -399,22 +412,16 @@ async function deleteEntity(projectId, entityId, entityType) { type: entityType, }) const newProject = await _removeElementFromMongoArray( - Project, projectId, path.mongo, entityId, + userId, deleteRootDoc ) return { entity, path, projectBeforeDeletion: project, newProject } } -async function renameEntity( - projectId, - entityId, - entityType, - newName, - callback -) { +async function renameEntity(projectId, entityId, entityType, newName, userId) { const project = await ProjectGetter.promises.getProjectWithoutLock( projectId, { rootFolder: true, name: true, overleaf: true } @@ -445,7 +452,14 @@ async function renameEntity( // we need to increment the project version number for any structure change const newProject = await Project.findOneAndUpdate( { _id: projectId, [entPath.mongo]: { $exists: true } }, - { $set: { [`${entPath.mongo}.name`]: newName }, $inc: { version: 1 } }, + { + $set: { + [`${entPath.mongo}.name`]: newName, + lastUpdated: new Date(), + lastUpdatedBy: userId, + }, + $inc: { version: 1 }, + }, { new: true } ).exec() if (newProject == null) { @@ -478,10 +492,10 @@ async function _insertDeletedFileReference(projectId, fileRef) { } async function _removeElementFromMongoArray( - model, modelId, path, elementId, + userId, deleteRootDoc = false ) { const nonArrayPath = path.slice(0, path.lastIndexOf('.')) @@ -490,11 +504,12 @@ async function _removeElementFromMongoArray( const update = { $pull: { [nonArrayPath]: { _id: elementId } }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } if (deleteRootDoc) { update.$unset = { rootDoc_id: 1 } } - return model.findOneAndUpdate(query, update, options).exec() + return Project.findOneAndUpdate(query, update, options).exec() } function _countElements(project) { @@ -522,7 +537,7 @@ function _countElements(project) { return countFolder(project.rootFolder[0]) } -async function _putElement(project, folderId, element, type) { +async function _putElement(project, folderId, element, type, userId) { if (element == null || element._id == null) { logger.warn( { projectId: project._id, folderId, element, type }, @@ -578,7 +593,11 @@ async function _putElement(project, folderId, element, type) { const mongoPath = `${path.mongo}.${pathSegment}` const newProject = await Project.findOneAndUpdate( { _id: project._id, [path.mongo]: { $exists: true } }, - { $push: { [mongoPath]: element }, $inc: { version: 1 } }, + { + $push: { [mongoPath]: element }, + $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, + }, { new: true } ).exec() if (newProject == null) { @@ -709,7 +728,11 @@ async function createNewFolderStructure(projectId, docEntries, fileEntries) { 'rootFolder.0.files.0': { $exists: false }, }, { - $set: { rootFolder: [rootFolder] }, + $set: { + rootFolder: [rootFolder], + // NOTE: Do not set lastUpdated/lastUpdatedBy here. They are both set when creating the initial record. + // The newly created clsi-cache record uses the lastUpdated timestamp of the initial record. Updating the lastUpdated timestamp here invalidates the cache record. + }, $inc: { version: 1 }, }, { diff --git a/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js b/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js index dd86dc4724..a3ae3e0bfa 100644 --- a/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js +++ b/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js @@ -296,7 +296,8 @@ const addDocWithRanges = wrapWithLock({ await ProjectEntityUpdateHandler._addDocAndSendToTpds( projectId, folderId, - doc + doc, + userId ) const docPath = result?.path?.fileSystem const projectHistoryId = project?.overleaf?.history?.id @@ -364,7 +365,8 @@ const addFile = wrapWithLock({ await ProjectEntityUpdateHandler._addFileAndSendToTpds( projectId, folderId, - fileRef + fileRef, + userId ) const projectHistoryId = project.overleaf?.history?.id const newFiles = [ @@ -382,12 +384,6 @@ const addFile = wrapWithLock({ { newFiles, newProject: project }, source ) - - ProjectUpdateHandler.promises - .markAsUpdated(projectId, new Date(), userId) - .catch(error => { - logger.error({ error }, 'failed to mark project as updated') - }) return { fileRef, folderId, createdBlob } }, }) @@ -434,7 +430,8 @@ const upsertDoc = wrapWithLock( await ProjectEntityMongoUpdateHandler.promises.replaceFileWithDoc( projectId, existingFile._id, - doc + doc, + userId ) await TpdsUpdateSender.promises.addDoc({ @@ -616,7 +613,8 @@ const upsertFile = wrapWithLock({ await ProjectEntityMongoUpdateHandler.promises.replaceDocWithFile( projectId, existingDoc._id, - fileRef + fileRef, + userId ) const projectHistoryId = project.overleaf?.history?.id await TpdsUpdateSender.promises.addFile({ @@ -699,7 +697,8 @@ const upsertDocWithPath = wrapWithLock( const { newFolders, folder } = await ProjectEntityUpdateHandler.promises.mkdirp.withoutLock( projectId, - folderPath + folderPath, + userId ) const { isNew, doc } = await ProjectEntityUpdateHandler.promises.upsertDoc.withoutLock( @@ -772,7 +771,8 @@ const upsertFileWithPath = wrapWithLock({ const { newFolders, folder } = await ProjectEntityUpdateHandler.promises.mkdirp.withoutLock( projectId, - folderPath + folderPath, + userId ) // this calls directly into the upsertFile main task (without the beforeLock part) const { @@ -818,7 +818,8 @@ const deleteEntity = wrapWithLock( await ProjectEntityMongoUpdateHandler.promises.deleteEntity( projectId, entityId, - entityType + entityType, + userId ) const subtreeListing = await ProjectEntityUpdateHandler._cleanUpEntity( projectBeforeDeletion, @@ -866,7 +867,7 @@ const deleteEntityWithPath = wrapWithLock( } ) -const mkdirp = wrapWithLock(async function (projectId, path) { +const mkdirp = wrapWithLock(async function (projectId, path, userId) { for (const folder of path.split('/')) { if (folder.length > 0 && !SafePath.isCleanFilename(folder)) { throw new Errors.InvalidNameError('invalid element name') @@ -875,32 +876,37 @@ const mkdirp = wrapWithLock(async function (projectId, path) { return await ProjectEntityMongoUpdateHandler.promises.mkdirp( projectId, path, + userId, { exactCaseMatch: false } ) }) -const mkdirpWithExactCase = wrapWithLock(async function (projectId, path) { - for (const folder of path.split('/')) { - if (folder.length > 0 && !SafePath.isCleanFilename(folder)) { - throw new Errors.InvalidNameError('invalid element name') +const mkdirpWithExactCase = wrapWithLock( + async function (projectId, path, userId) { + for (const folder of path.split('/')) { + if (folder.length > 0 && !SafePath.isCleanFilename(folder)) { + throw new Errors.InvalidNameError('invalid element name') + } } + return await ProjectEntityMongoUpdateHandler.promises.mkdirp( + projectId, + path, + userId, + { exactCaseMatch: true } + ) } - return await ProjectEntityMongoUpdateHandler.promises.mkdirp( - projectId, - path, - { exactCaseMatch: true } - ) -}) +) const addFolder = wrapWithLock( - async function (projectId, parentFolderId, folderName) { + async function (projectId, parentFolderId, folderName, userId) { if (!SafePath.isCleanFilename(folderName)) { throw new Errors.InvalidNameError('invalid element name') } return await ProjectEntityMongoUpdateHandler.promises.addFolder( projectId, parentFolderId, - folderName + folderName, + userId ) } ) @@ -929,7 +935,8 @@ const moveEntity = wrapWithLock( projectId, entityId, destFolderId, - entityType + entityType, + userId ) const projectHistoryId = project.overleaf?.history?.id @@ -988,7 +995,8 @@ const renameEntity = wrapWithLock( projectId, entityId, entityType, - newName + newName, + userId ) const projectHistoryId = project.overleaf?.history?.id @@ -1115,7 +1123,8 @@ const convertDocToFile = wrapWithLock({ await ProjectEntityMongoUpdateHandler.promises.replaceDocWithFile( projectId, doc._id, - fileRef + fileRef, + userId ) const projectHistoryId = project.overleaf?.history?.id await DocumentUpdaterHandler.promises.updateProjectStructure( @@ -1276,14 +1285,15 @@ const ProjectEntityUpdateHandler = { appendToDocWithPath: appendToDoc, }, - async _addDocAndSendToTpds(projectId, folderId, doc) { + async _addDocAndSendToTpds(projectId, folderId, doc, userId) { let result, project try { ;({ result, project } = await ProjectEntityMongoUpdateHandler.promises.addDoc( projectId, folderId, - doc + doc, + userId )) } catch (err) { throw OError.tag(err, 'error adding file with project', { @@ -1328,14 +1338,15 @@ const ProjectEntityUpdateHandler = { } }, - async _addFileAndSendToTpds(projectId, folderId, fileRef) { + async _addFileAndSendToTpds(projectId, folderId, fileRef, userId) { let result, project try { ;({ result, project } = await ProjectEntityMongoUpdateHandler.promises.addFile( projectId, folderId, - fileRef + fileRef, + userId )) } catch (err) { throw OError.tag(err, 'error adding file with project', { @@ -1382,7 +1393,8 @@ const ProjectEntityUpdateHandler = { } = await ProjectEntityMongoUpdateHandler.promises.replaceFileWithNew( projectId, fileId, - newFileRef + newFileRef, + userId ) const oldFiles = [ @@ -1410,11 +1422,6 @@ const ProjectEntityUpdateHandler = { projectName: project.name, folderId, }) - ProjectUpdateHandler.promises - .markAsUpdated(projectId, new Date(), userId) - .catch(error => { - logger.error({ error }, 'failed to mark project as updated') - }) await DocumentUpdaterHandler.promises.updateProjectStructure( projectId, @@ -1538,7 +1545,8 @@ const ProjectEntityUpdateHandler = { projectId, entityId, entityType, - rename.newName + rename.newName, + null // unset lastUpdatedBy ) // update the renamed entity for the resync diff --git a/services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateHandler.mjs b/services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateHandler.mjs index c17b54ff1e..219db88b12 100644 --- a/services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateHandler.mjs +++ b/services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateHandler.mjs @@ -180,7 +180,11 @@ async function createFolder(userId, projectId, projectName, path) { return null } - const folder = await UpdateMerger.promises.createFolder(project._id, path) + const folder = await UpdateMerger.promises.createFolder( + project._id, + path, + userId + ) return { folderId: folder._id, parentFolderId: folder.parentFolder_id, diff --git a/services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js b/services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js index e67d5fc30d..f68d366d8e 100644 --- a/services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js +++ b/services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js @@ -176,10 +176,11 @@ async function _readFileIntoTextArray(path) { return lines } -async function createFolder(projectId, path) { +async function createFolder(projectId, path, userId) { const { lastFolder: folder } = await EditorController.promises.mkdirp( projectId, - path + path, + userId ) return folder } diff --git a/services/web/app/src/Features/Uploads/ProjectUploadController.mjs b/services/web/app/src/Features/Uploads/ProjectUploadController.mjs index da2847d108..a3bc434ed7 100644 --- a/services/web/app/src/Features/Uploads/ProjectUploadController.mjs +++ b/services/web/app/src/Features/Uploads/ProjectUploadController.mjs @@ -68,6 +68,7 @@ async function uploadFile(req, res, next) { const name = req.body.name const path = req.file?.path const projectId = req.params.Project_id + const userId = SessionManager.getLoggedInUserId(req.session) let { folder_id: folderId } = req.query if (name == null || name.length === 0 || name.length > 150) { return res.status(422).json({ @@ -87,13 +88,12 @@ async function uploadFile(req, res, next) { }) const { lastFolder } = await EditorController.promises.mkdirp( projectId, - Path.dirname(Path.join('/', path.fileSystem, relativePath)) + Path.dirname(Path.join('/', path.fileSystem, relativePath)), + userId ) folderId = lastFolder._id } - const userId = SessionManager.getLoggedInUserId(req.session) - return FileSystemImportManager.addEntity( userId, projectId, diff --git a/services/web/scripts/check_project_files.js b/services/web/scripts/check_project_files.js index 41fe9ecda7..e827895e66 100644 --- a/services/web/scripts/check_project_files.js +++ b/services/web/scripts/check_project_files.js @@ -136,7 +136,8 @@ async function createRecoveryFolder(projectId) { const recoveryFolder = `recovered-${Date.now()}` const { folder } = await ProjectEntityMongoUpdateHandler.promises.mkdirp( new ObjectId(projectId), - recoveryFolder + recoveryFolder, + null // unset lastUpdatedBy ) console.log('Created recovery folder:', folder._id.toString()) return folder @@ -149,7 +150,8 @@ async function restoreMissingDocs(projectId, folder, missingDocs) { await ProjectEntityMongoUpdateHandler.promises.addDoc( new ObjectId(projectId), folder._id, - doc + doc, + null // unset lastUpdatedBy ) console.log('Restored doc to filetree:', doc._id.toString()) } catch (err) { diff --git a/services/web/scripts/delete_dangling_file_refs.mjs b/services/web/scripts/delete_dangling_file_refs.mjs index 262376aa7d..39383bed79 100644 --- a/services/web/scripts/delete_dangling_file_refs.mjs +++ b/services/web/scripts/delete_dangling_file_refs.mjs @@ -104,7 +104,8 @@ async function deleteDoc(projectId, docId) { await ProjectEntityMongoUpdateHandler.promises.deleteEntity( projectId, docId, - 'doc' + 'doc', + null // unset lastUpdatedBy ) } } @@ -115,7 +116,8 @@ async function deleteFile(projectId, fileId) { await ProjectEntityMongoUpdateHandler.promises.deleteEntity( projectId, fileId, - 'file' + 'file', + null // unset lastUpdatedBy ) } } diff --git a/services/web/scripts/fix_malformed_filetree.mjs b/services/web/scripts/fix_malformed_filetree.mjs index 2c2971ecfd..2358bed850 100644 --- a/services/web/scripts/fix_malformed_filetree.mjs +++ b/services/web/scripts/fix_malformed_filetree.mjs @@ -17,6 +17,7 @@ import fs from 'node:fs' import logger from '@overleaf/logger' const { ObjectId } = mongodb +const lastUpdated = new Date() const argv = minimist(process.argv.slice(2), { string: ['logs'], @@ -157,6 +158,8 @@ async function fixRootFolder(projectId) { fileRefs: [], }, ], + lastUpdated, + lastUpdatedBy: null, // unset lastUpdatedBy }, } ) @@ -185,6 +188,10 @@ async function removeNulls(projectId, _id) { [`${path}.docs`]: null, [`${path}.fileRefs`]: null, }, + $set: { + lastUpdated, + lastUpdatedBy: null, // unset lastUpdatedBy + }, } ) return result.modifiedCount @@ -196,7 +203,7 @@ async function removeNulls(projectId, _id) { async function fixArray(projectId, path) { const result = await db.projects.updateOne( { _id: new ObjectId(projectId), [path]: { $not: { $type: 'array' } } }, - { $set: { [path]: [] } } + { $set: { [path]: [], lastUpdated, lastUpdatedBy: null } } ) return result.modifiedCount } @@ -207,7 +214,13 @@ async function fixArray(projectId, path) { async function fixFolderId(projectId, path) { const result = await db.projects.updateOne( { _id: new ObjectId(projectId), [path]: { $exists: false } }, - { $set: { [path]: new ObjectId() } } + { + $set: { + [path]: new ObjectId(), + lastUpdated, + lastUpdatedBy: null, // unset lastUpdatedBy + }, + } ) return result.modifiedCount } @@ -218,7 +231,13 @@ async function fixFolderId(projectId, path) { async function removeElementsWithoutIds(projectId, path) { const result = await db.projects.updateOne( { _id: new ObjectId(projectId), [path]: { $type: 'array' } }, - { $pull: { [path]: { _id: null } } } + { + $pull: { [path]: { _id: null } }, + $set: { + lastUpdated, + lastUpdatedBy: null, // unset lastUpdatedBy + }, + } ) return result.modifiedCount } @@ -245,7 +264,13 @@ async function fixName(projectId, _id) { const pathToName = `${path}.name` const result = await db.projects.updateOne( { _id: new ObjectId(projectId), [pathToName]: { $in: [null, ''] } }, - { $set: { [pathToName]: name } } + { + $set: { + [pathToName]: name, + lastUpdated, + lastUpdatedBy: null, // unset lastUpdatedBy + }, + } ) return result.modifiedCount } diff --git a/services/web/scripts/fix_oversized_docs.mjs b/services/web/scripts/fix_oversized_docs.mjs index 07f66a76cf..9f2e250b92 100644 --- a/services/web/scripts/fix_oversized_docs.mjs +++ b/services/web/scripts/fix_oversized_docs.mjs @@ -76,7 +76,8 @@ async function processDoc(projectId, docId) { await ProjectEntityMongoUpdateHandler.promises.replaceDocWithFile( new ObjectId(projectId), new ObjectId(docId), - fileRef + fileRef, + null // unset lastUpdatedBy ) await deleteDocFromMongo(projectId, doc) await deleteDocFromRedis(projectId, docId) diff --git a/services/web/test/acceptance/src/MalformedFiletreesTests.mjs b/services/web/test/acceptance/src/MalformedFiletreesTests.mjs index 744781f8c8..16282d592d 100644 --- a/services/web/test/acceptance/src/MalformedFiletreesTests.mjs +++ b/services/web/test/acceptance/src/MalformedFiletreesTests.mjs @@ -5,6 +5,10 @@ import logger from '@overleaf/logger' import { filterOutput } from './helpers/settings.mjs' import { db, ObjectId } from '../../../app/src/infrastructure/mongodb.js' +const lastUpdated = new Date(42) +const lastUpdatedBy = new ObjectId() +const lastUpdatedChanged = new Date(1337) + async function runScriptFind() { try { const result = await promisify(exec)( @@ -36,7 +40,18 @@ async function runScriptFix(instructions) { const findProjects = () => db.projects - .find({}, { projection: { rootFolder: 1, _id: 1, version: 1 } }) + .find( + {}, + { + projection: { + rootFolder: 1, + _id: 1, + version: 1, + lastUpdated: 1, + lastUpdatedBy: 1, + }, + } + ) .toArray() const projectId = new ObjectId() @@ -75,13 +90,15 @@ const wellFormedProject = { fileRefs: [wellFormedFileRef('fr00'), wellFormedFileRef('fr01')], }, ], + lastUpdated, + lastUpdatedBy, } const testCases = [ ...[{}, { rootFolder: undefined }, { rootFolder: '1234' }].map( (project, idx) => ({ name: `bad rootFolder ${idx + 1}`, - project: { _id: projectId, ...project }, + project: { _id: projectId, ...project, lastUpdated, lastUpdatedBy }, expectFind: [ { _id: null, @@ -98,7 +115,7 @@ const testCases = [ { name: `missing rootFolder`, - project: { _id: projectId, rootFolder: [] }, + project: { _id: projectId, rootFolder: [], lastUpdated, lastUpdatedBy }, expectFind: [ { _id: null, @@ -123,6 +140,8 @@ const testCases = [ docs: [], }, ], + lastUpdated: lastUpdatedChanged, + lastUpdatedBy: null, }) }, }, @@ -132,6 +151,8 @@ const testCases = [ project: { _id: projectId, rootFolder: [{ _id: '1234' }], + lastUpdated, + lastUpdatedBy, }, expectFind: [ { reason: 'bad folder id', path: 'rootFolder.0._id' }, @@ -154,6 +175,8 @@ const testCases = [ project: { _id: projectId, rootFolder: [{ _id: rootFolderId }], + lastUpdated, + lastUpdatedBy, }, expectFind: [ { reason: 'bad folder name', path: 'rootFolder.0.name' }, @@ -180,6 +203,8 @@ const testCases = [ name: 'rootFolder', }, ], + lastUpdated: lastUpdatedChanged, + lastUpdatedBy: null, }) }, }, @@ -197,6 +222,8 @@ const testCases = [ fileRefs: [null, null], }, ], + lastUpdated, + lastUpdatedBy, }, expectFind: [ { @@ -235,6 +262,8 @@ const testCases = [ folders: [], }, ], + lastUpdated: lastUpdatedChanged, + lastUpdatedBy: null, }) }, }, @@ -255,6 +284,8 @@ const testCases = [ fileRefs: [{ _id: null, name: 'ref-a' }, { name: 'ref-b' }], }, ], + lastUpdated, + lastUpdatedBy, }, expectFind: [ { reason: 'bad folder id', path: 'rootFolder.0.folders.0._id', _id: 123 }, @@ -291,6 +322,8 @@ const testCases = [ fileRefs: [], }, ], + lastUpdated: lastUpdatedChanged, + lastUpdatedBy: null, }) }, }, @@ -314,6 +347,8 @@ const testCases = [ ], }, ], + lastUpdated, + lastUpdatedBy, }, expectFind: [ { @@ -386,6 +421,8 @@ const testCases = [ ], }, ], + lastUpdated: lastUpdatedChanged, + lastUpdatedBy: null, }) }, }, @@ -403,6 +440,8 @@ const testCases = [ ], }, ], + lastUpdated, + lastUpdatedBy, }, expectFind: [ { path: 'rootFolder.0.fileRefs.0.hash', _id: strId('fa') }, @@ -442,6 +481,8 @@ const testCases = [ fileRefs: [null, null, { ...wellFormedFileRef('fr02'), name: null }], }, ], + lastUpdated, + lastUpdatedBy, }, expectFind: [ { @@ -508,6 +549,8 @@ const testCases = [ fileRefs: [{ ...wellFormedFileRef('fr02'), name: 'untitled' }], }, ], + lastUpdated: lastUpdatedChanged, + lastUpdatedBy: null, }) }, }, @@ -539,6 +582,8 @@ const testCases = [ fileRefs: [], }, ], + lastUpdated, + lastUpdatedBy, }, expectFind: [ { @@ -637,6 +682,8 @@ const testCases = [ fileRefs: [], }, ], + lastUpdated: lastUpdatedChanged, + lastUpdatedBy: null, }) }, }, @@ -674,6 +721,9 @@ describe('find_malformed_filetrees and fix_malformed_filetree scripts', function expect(expectFixStdout).to.be.a('string') expect(stdout).to.include(expectFixStdout) const [updatedProject] = await findProjects() + if (updatedProject.lastUpdated > lastUpdated) { + updatedProject.lastUpdated = lastUpdatedChanged + } expectProject(updatedProject) }) } diff --git a/services/web/test/unit/src/Compile/ClsiManagerTests.js b/services/web/test/unit/src/Compile/ClsiManagerTests.js index 65c3d310d0..b9b7d9af37 100644 --- a/services/web/test/unit/src/Compile/ClsiManagerTests.js +++ b/services/web/test/unit/src/Compile/ClsiManagerTests.js @@ -13,6 +13,8 @@ const GLOBAL_BLOB_HASH = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' describe('ClsiManager', function () { beforeEach(function () { + tk.freeze(Date.now()) + this.user_id = 'user-id' this.project = { _id: 'project-id', @@ -182,7 +184,6 @@ describe('ClsiManager', function () { '../History/HistoryManager': this.HistoryManager, }, }) - tk.freeze(Date.now()) }) after(function () { diff --git a/services/web/test/unit/src/Editor/EditorControllerTests.js b/services/web/test/unit/src/Editor/EditorControllerTests.js index 798f2f9499..2dd11bdf0f 100644 --- a/services/web/test/unit/src/Editor/EditorControllerTests.js +++ b/services/web/test/unit/src/Editor/EditorControllerTests.js @@ -518,7 +518,12 @@ describe('EditorController', function () { it('should add the folder using the project entity handler', function () { return this.ProjectEntityUpdateHandler.addFolder - .calledWith(this.project_id, this.folder_id, this.folderName) + .calledWith( + this.project_id, + this.folder_id, + this.folderName, + this.user_id + ) .should.equal(true) }) @@ -540,6 +545,7 @@ describe('EditorController', function () { (this.folderA = { _id: 2, parentFolder_id: 1 }), (this.folderB = { _id: 3, parentFolder_id: 2 }), ] + this.userId = new ObjectId().toString() this.EditorController._notifyProjectUsersOfNewFolders = sinon .stub() .yields() @@ -549,13 +555,14 @@ describe('EditorController', function () { return this.EditorController.mkdirp( this.project_id, this.path, + this.userId, this.callback ) }) it('should create the folder using the project entity handler', function () { return this.ProjectEntityUpdateHandler.mkdirp - .calledWith(this.project_id, this.path) + .calledWith(this.project_id, this.path, this.userId) .should.equal(true) }) diff --git a/services/web/test/unit/src/History/RestoreManagerTests.js b/services/web/test/unit/src/History/RestoreManagerTests.js index d4bb9fc9ff..2474425bfb 100644 --- a/services/web/test/unit/src/History/RestoreManagerTests.js +++ b/services/web/test/unit/src/History/RestoreManagerTests.js @@ -81,7 +81,7 @@ describe('RestoreManager', function () { it('should find the root folder', function () { this.RestoreManager.promises._findOrCreateFolder - .calledWith(this.project_id, '') + .calledWith(this.project_id, '', this.user_id) .should.equal(true) }) @@ -116,7 +116,7 @@ describe('RestoreManager', function () { it('should find the folder', function () { this.RestoreManager.promises._findOrCreateFolder - .calledWith(this.project_id, 'foo') + .calledWith(this.project_id, 'foo', this.user_id) .should.equal(true) }) @@ -143,13 +143,14 @@ describe('RestoreManager', function () { }) this.result = await this.RestoreManager.promises._findOrCreateFolder( this.project_id, - 'folder/name' + 'folder/name', + this.user_id ) }) it('should look up or create the folder', function () { this.EditorController.promises.mkdirp - .calledWith(this.project_id, 'folder/name') + .calledWith(this.project_id, 'folder/name', this.user_id) .should.equal(true) }) diff --git a/services/web/test/unit/src/Project/ProjectEntityMongoUpdateHandlerTests.js b/services/web/test/unit/src/Project/ProjectEntityMongoUpdateHandlerTests.js index 53b459ff05..b1b29c5145 100644 --- a/services/web/test/unit/src/Project/ProjectEntityMongoUpdateHandlerTests.js +++ b/services/web/test/unit/src/Project/ProjectEntityMongoUpdateHandlerTests.js @@ -12,6 +12,7 @@ const MODULE_PATH = describe('ProjectEntityMongoUpdateHandler', function () { beforeEach(function () { + tk.freeze(new Date()) this.doc = { _id: new ObjectId(), name: 'test-doc.txt', @@ -209,19 +210,13 @@ describe('ProjectEntityMongoUpdateHandler', function () { afterEach(function () { this.DeletedFileMock.restore() this.ProjectMock.restore() - }) - - beforeEach(function () { - tk.freeze(Date.now()) - }) - - afterEach(function () { tk.reset() }) describe('addDoc', function () { beforeEach(async function () { const doc = { _id: new ObjectId(), name: 'other.txt' } + const userId = new ObjectId().toString() this.ProjectMock.expects('findOneAndUpdate') .withArgs( { @@ -231,6 +226,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { { $push: { 'rootFolder.0.folders.0.docs': doc }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -238,7 +234,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { this.result = await this.subject.promises.addDoc( this.project._id, this.folder._id, - doc + doc, + userId ) }) @@ -260,7 +257,9 @@ describe('ProjectEntityMongoUpdateHandler', function () { }) describe('addFile', function () { + let userId beforeEach(function () { + userId = new ObjectId().toString() this.newFile = { _id: new ObjectId(), name: 'picture.jpg' } this.ProjectMock.expects('findOneAndUpdate') .withArgs( @@ -271,6 +270,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { { $push: { 'rootFolder.0.folders.0.fileRefs': this.newFile }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -282,7 +282,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { this.result = await this.subject.promises.addFile( this.project._id, this.folder._id, - this.newFile + this.newFile, + userId ) }) @@ -318,7 +319,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { this.subject.promises.addFile( this.project._id, this.folder._id, - this.newFile + this.newFile, + userId ) ).to.be.rejected }) @@ -327,6 +329,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('addFolder', function () { beforeEach(async function () { + const userId = new ObjectId().toString() const folderName = 'New folder' this.FolderModel.withArgs({ name: folderName }).returns({ _id: new ObjectId(), @@ -345,6 +348,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { }), }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -352,7 +356,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { await this.subject.promises.addFolder( this.project._id, this.folder._id, - folderName + folderName, + userId ) }) @@ -393,6 +398,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { 'rootFolder.0.fileRefs.0.created': sinon.match.date, 'rootFolder.0.fileRefs.0.linkedFileData': newFile.linkedFileData, 'rootFolder.0.fileRefs.0.hash': newFile.hash, + lastUpdated: new Date(), + lastUpdatedBy: 'userId', }, $inc: { version: 1, @@ -408,7 +415,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { await this.subject.promises.replaceFileWithNew( this.project._id, this.file._id, - newFile + newFile, + 'userId' ) }) @@ -460,6 +468,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('when the path is a new folder at the top level', function () { beforeEach(async function () { + const userId = new ObjectId().toString() this.newFolder = { _id: new ObjectId(), name: 'new-folder' } this.FolderModel.returns(this.newFolder) this.exactCaseMatch = false @@ -469,6 +478,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { { $push: { 'rootFolder.0.folders': this.newFolder }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -476,6 +486,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { this.result = await this.subject.promises.mkdirp( this.project._id, '/new-folder/', + userId, { exactCaseMatch: this.exactCaseMatch } ) }) @@ -504,6 +515,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('adding a subfolder', function () { beforeEach(async function () { + const userId = new ObjectId().toString() this.newFolder = { _id: new ObjectId(), name: 'new-folder' } this.FolderModel.returns(this.newFolder) this.ProjectMock.expects('findOneAndUpdate') @@ -519,13 +531,15 @@ describe('ProjectEntityMongoUpdateHandler', function () { }), }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') .resolves(this.project) this.result = await this.subject.promises.mkdirp( this.project._id, - '/test-folder/new-folder' + '/test-folder/new-folder', + userId ) }) @@ -547,7 +561,9 @@ describe('ProjectEntityMongoUpdateHandler', function () { }) describe('when mutliple folders are missing', async function () { + let userId beforeEach(function () { + userId = new ObjectId().toString() this.folder1 = { _id: new ObjectId(), name: 'folder1' } this.folder1Path = { fileSystem: '/test-folder/folder1', @@ -593,6 +609,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { }), }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -610,6 +627,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { }), }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -629,7 +647,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { beforeEach(async function () { this.result = await this.subject.promises.mkdirp( this.project._id, - path + path, + userId ) }) @@ -661,6 +680,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('moveEntity', function () { describe('moving a doc into a different folder', function () { beforeEach(async function () { + const userId = new ObjectId().toString() this.pathAfterMove = { fileSystem: '/somewhere/else.txt', } @@ -685,6 +705,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { { $push: { 'rootFolder.0.folders.0.docs': this.doc }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -695,6 +716,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { { $pull: { 'rootFolder.0.docs': { _id: this.doc._id } }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -703,7 +725,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { this.project._id, this.doc._id, this.folder._id, - 'doc' + 'doc', + userId ) }) @@ -770,12 +793,14 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('deleteEntity', function () { beforeEach(async function () { + const userId = new ObjectId().toString() this.ProjectMock.expects('findOneAndUpdate') .withArgs( { _id: this.project._id }, { $pull: { 'rootFolder.0.docs': { _id: this.doc._id } }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -783,7 +808,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { await this.subject.promises.deleteEntity( this.project._id, this.doc._id, - 'doc' + 'doc', + userId ) }) @@ -795,6 +821,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('renameEntity', function () { describe('happy path', function () { beforeEach(async function () { + const userId = new ObjectId().toString() this.newName = 'new.tex' this.oldDocs = ['old-doc'] this.oldFiles = ['old-file'] @@ -812,7 +839,11 @@ describe('ProjectEntityMongoUpdateHandler', function () { .withArgs( { _id: this.project._id, 'rootFolder.0.docs.0': { $exists: true } }, { - $set: { 'rootFolder.0.docs.0.name': this.newName }, + $set: { + 'rootFolder.0.docs.0.name': this.newName, + lastUpdated: new Date(), + lastUpdatedBy: userId, + }, $inc: { version: 1 }, } ) @@ -822,7 +853,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { this.project._id, this.doc._id, 'doc', - this.newName + this.newName, + userId ) }) @@ -864,7 +896,9 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('_putElement', function () { describe('updating the project', function () { describe('when the parent folder is given', function () { + let userId beforeEach(function () { + userId = new ObjectId().toString() this.newFile = { _id: new ObjectId(), name: 'new file.png' } this.ProjectMock.expects('findOneAndUpdate') .withArgs( @@ -875,6 +909,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { { $push: { 'rootFolder.0.folders.0.fileRefs': this.newFile }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -886,7 +921,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { this.project, this.folder._id, this.newFile, - 'files' + 'files', + userId ) this.ProjectMock.verify() }) @@ -896,7 +932,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { this.project, this.folder._id, this.newFile, - 'file' + 'file', + userId ) this.ProjectMock.verify() }) @@ -998,6 +1035,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('when the parent folder is not given', function () { it('should default to root folder insert', async function () { + const userId = new ObjectId().toString() this.newFile = { _id: new ObjectId(), name: 'new file.png' } this.ProjectMock.expects('findOneAndUpdate') .withArgs( @@ -1005,6 +1043,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { { $push: { 'rootFolder.0.fileRefs': this.newFile }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, } ) .chain('exec') @@ -1013,7 +1052,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { this.project, this.rootFolder._id, this.newFile, - 'file' + 'file', + userId ) }) }) @@ -1098,6 +1138,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('replaceDocWithFile', function () { it('should simultaneously remove the doc and add the file', async function () { + const userId = new ObjectId().toString() this.ProjectMock.expects('findOneAndUpdate') .withArgs( { _id: this.project._id, 'rootFolder.0': { $exists: true } }, @@ -1105,6 +1146,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { $pull: { 'rootFolder.0.docs': { _id: this.doc._id } }, $push: { 'rootFolder.0.fileRefs': this.file }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, }, { new: true } ) @@ -1113,7 +1155,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { await this.subject.promises.replaceDocWithFile( this.project._id, this.doc._id, - this.file + this.file, + userId ) this.ProjectMock.verify() }) @@ -1121,6 +1164,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { describe('replaceFileWithDoc', function () { it('should simultaneously remove the file and add the doc', async function () { + const userId = new ObjectId().toString() this.ProjectMock.expects('findOneAndUpdate') .withArgs( { _id: this.project._id, 'rootFolder.0': { $exists: true } }, @@ -1128,6 +1172,7 @@ describe('ProjectEntityMongoUpdateHandler', function () { $pull: { 'rootFolder.0.fileRefs': { _id: this.file._id } }, $push: { 'rootFolder.0.docs': this.doc }, $inc: { version: 1 }, + $set: { lastUpdated: new Date(), lastUpdatedBy: userId }, }, { new: true } ) @@ -1136,7 +1181,8 @@ describe('ProjectEntityMongoUpdateHandler', function () { await this.subject.promises.replaceFileWithDoc( this.project._id, this.file._id, - this.doc + this.doc, + userId ) this.ProjectMock.verify() }) diff --git a/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js b/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js index de4a67be93..e8ed371c91 100644 --- a/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js +++ b/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js @@ -747,13 +747,6 @@ describe('ProjectEntityUpdateHandler', function () { .should.equal(true) }) - it('should mark the project as updated', function () { - const args = this.ProjectUpdater.promises.markAsUpdated.args[0] - args[0].should.equal(projectId) - args[1].should.exist - args[2].should.equal(userId) - }) - it('sends the change in project structure to the doc updater', function () { const newFiles = [ { @@ -1181,7 +1174,8 @@ describe('ProjectEntityUpdateHandler', function () { this.ProjectEntityMongoUpdateHandler.promises.replaceFileWithNew.should.have.been.calledWith( projectId, this.existingFile._id, - this.file + this.file, + userId ) }) @@ -1198,13 +1192,6 @@ describe('ProjectEntityUpdateHandler', function () { }) }) - it('should mark the project as updated', function () { - const args = this.ProjectUpdater.promises.markAsUpdated.args[0] - args[0].should.equal(projectId) - args[1].should.exist - args[2].should.equal(userId) - }) - it('updates the project structure in the doc updater', function () { const oldFiles = [ { @@ -1394,7 +1381,12 @@ describe('ProjectEntityUpdateHandler', function () { it('replaces the existing doc with a file', function () { expect( this.ProjectEntityMongoUpdateHandler.promises.replaceDocWithFile - ).to.have.been.calledWith(projectId, this.existingDoc._id, this.newFile) + ).to.have.been.calledWith( + projectId, + this.existingDoc._id, + this.newFile, + userId + ) }) it('updates the doc structure', function () { @@ -1475,7 +1467,7 @@ describe('ProjectEntityUpdateHandler', function () { it('creates any necessary folders', function () { this.ProjectEntityUpdateHandler.promises.mkdirp.withoutLock - .calledWith(projectId, '/folder') + .calledWith(projectId, '/folder', userId) .should.equal(true) }) @@ -1620,7 +1612,7 @@ describe('ProjectEntityUpdateHandler', function () { it('creates any necessary folders', function () { this.ProjectEntityUpdateHandler.promises.mkdirp.withoutLock - .calledWith(projectId, '/folder') + .calledWith(projectId, '/folder', userId) .should.equal(true) }) @@ -1767,7 +1759,7 @@ describe('ProjectEntityUpdateHandler', function () { it('deletes the entity in mongo', function () { this.ProjectEntityMongoUpdateHandler.promises.deleteEntity - .calledWith(projectId, docId, 'doc') + .calledWith(projectId, docId, 'doc', userId) .should.equal(true) }) @@ -1873,12 +1865,17 @@ describe('ProjectEntityUpdateHandler', function () { beforeEach(function (done) { this.docPath = '/folder/doc.tex' this.ProjectEntityMongoUpdateHandler.promises.mkdirp.resolves({}) - this.ProjectEntityUpdateHandler.mkdirp(projectId, this.docPath, done) + this.ProjectEntityUpdateHandler.mkdirp( + projectId, + this.docPath, + userId, + done + ) }) it('calls ProjectEntityMongoUpdateHandler', function () { this.ProjectEntityMongoUpdateHandler.promises.mkdirp - .calledWith(projectId, this.docPath) + .calledWith(projectId, this.docPath, userId) .should.equal(true) }) }) @@ -1890,13 +1887,14 @@ describe('ProjectEntityUpdateHandler', function () { this.ProjectEntityUpdateHandler.mkdirpWithExactCase( projectId, this.docPath, + userId, done ) }) it('calls ProjectEntityMongoUpdateHandler', function () { this.ProjectEntityMongoUpdateHandler.promises.mkdirp - .calledWith(projectId, this.docPath, { exactCaseMatch: true }) + .calledWith(projectId, this.docPath, userId, { exactCaseMatch: true }) .should.equal(true) }) }) @@ -1911,13 +1909,14 @@ describe('ProjectEntityUpdateHandler', function () { projectId, this.parentFolderId, this.folderName, + userId, done ) }) it('calls ProjectEntityMongoUpdateHandler', function () { this.ProjectEntityMongoUpdateHandler.promises.addFolder - .calledWith(projectId, this.parentFolderId, this.folderName) + .calledWith(projectId, this.parentFolderId, this.folderName, userId) .should.equal(true) }) }) @@ -1973,7 +1972,7 @@ describe('ProjectEntityUpdateHandler', function () { it('moves the entity in mongo', function () { this.ProjectEntityMongoUpdateHandler.promises.moveEntity - .calledWith(projectId, docId, folderId, 'doc') + .calledWith(projectId, docId, folderId, 'doc', userId) .should.equal(true) }) @@ -2035,7 +2034,7 @@ describe('ProjectEntityUpdateHandler', function () { it('moves the entity in mongo', function () { this.ProjectEntityMongoUpdateHandler.promises.renameEntity - .calledWith(projectId, docId, 'doc', this.newDocName) + .calledWith(projectId, docId, 'doc', this.newDocName, userId) .should.equal(true) }) @@ -2320,25 +2319,29 @@ describe('ProjectEntityUpdateHandler', function () { projectId, 'doc3', 'doc', - 'duplicate.tex (1)' + 'duplicate.tex (1)', + null ) expect(renameEntity).to.have.been.calledWith( projectId, 'doc5', 'doc', - 'duplicate.tex (2)' + 'duplicate.tex (2)', + null ) expect(renameEntity).to.have.been.calledWith( projectId, 'file3', 'file', - 'duplicate.jpg (1)' + 'duplicate.jpg (1)', + null ) expect(renameEntity).to.have.been.calledWith( projectId, 'file4', 'file', - 'another dupe (23)' + 'another dupe (23)', + null ) }) @@ -2410,25 +2413,29 @@ describe('ProjectEntityUpdateHandler', function () { projectId, 'doc1', 'doc', - '_d_e_f_test.tex' + '_d_e_f_test.tex', + null ) expect(renameEntity).to.have.been.calledWith( projectId, 'doc2', 'doc', - 'untitled' + 'untitled', + null ) expect(renameEntity).to.have.been.calledWith( projectId, 'file1', 'file', - 'A_.png' + 'A_.png', + null ) expect(renameEntity).to.have.been.calledWith( projectId, 'file2', 'file', - 'A_.png (1)' + 'A_.png (1)', + null ) }) @@ -2501,7 +2508,8 @@ describe('ProjectEntityUpdateHandler', function () { projectId, 'folder2', 'folder', - 'bad_' + 'bad_', + null ) }) @@ -2558,7 +2566,8 @@ describe('ProjectEntityUpdateHandler', function () { projectId, 'doc1', 'doc', - 'chapters (1)' + 'chapters (1)', + null ) }) @@ -2929,7 +2938,7 @@ describe('ProjectEntityUpdateHandler', function () { this.ProjectEntityUpdateHandler.convertDocToFile( this.project._id, this.doc._id, - this.user._id, + userId, this.source, done ) @@ -2960,7 +2969,12 @@ describe('ProjectEntityUpdateHandler', function () { it('replaces the doc with the file', function () { expect( this.ProjectEntityMongoUpdateHandler.promises.replaceDocWithFile - ).to.have.been.calledWith(this.project._id, this.doc._id, this.file) + ).to.have.been.calledWith( + this.project._id, + this.doc._id, + this.file, + userId + ) }) it('notifies document updater of changes', function () { @@ -2969,7 +2983,7 @@ describe('ProjectEntityUpdateHandler', function () { ).to.have.been.calledWith( this.project._id, this.project.overleaf.history.id, - this.user._id, + userId, { oldDocs: [{ doc: this.doc, path: this.path }], newFiles: [ diff --git a/services/web/test/unit/src/Subscription/RecurlyWrapperTests.js b/services/web/test/unit/src/Subscription/RecurlyWrapperTests.js index ac353cddd7..d4bdd9d9b4 100644 --- a/services/web/test/unit/src/Subscription/RecurlyWrapperTests.js +++ b/services/web/test/unit/src/Subscription/RecurlyWrapperTests.js @@ -107,6 +107,7 @@ const mockApiRequest = function (options) { describe('RecurlyWrapper', function () { beforeEach(function () { + tk.freeze(Date.now()) // freeze the time for these tests this.settings = { plans: [ { @@ -134,7 +135,6 @@ describe('RecurlyWrapper', function () { fetchStringWithResponse: sinon.stub(), RequestFailedError, } - tk.freeze(Date.now()) // freeze the time for these tests this.RecurlyWrapper = SandboxedModule.require(modulePath, { requires: { '@overleaf/settings': this.settings, diff --git a/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateHandlerTests.mjs b/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateHandlerTests.mjs index 5d52417434..a5ca099b5b 100644 --- a/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateHandlerTests.mjs +++ b/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateHandlerTests.mjs @@ -540,7 +540,8 @@ function expectFolderUpdateProcessed() { it('processes the folder update', function () { expect(this.UpdateMerger.promises.createFolder).to.have.been.calledWith( this.projects.active1._id, - this.folderPath + this.folderPath, + this.userId ) }) } diff --git a/services/web/test/unit/src/Uploads/ProjectUploadControllerTests.mjs b/services/web/test/unit/src/Uploads/ProjectUploadControllerTests.mjs index 8db1e9536e..35682f346c 100644 --- a/services/web/test/unit/src/Uploads/ProjectUploadControllerTests.mjs +++ b/services/web/test/unit/src/Uploads/ProjectUploadControllerTests.mjs @@ -267,7 +267,8 @@ describe('ProjectUploadController', function () { this.EditorController.promises.mkdirp.should.be.calledWith( this.project_id, - '/test/foo/bar' + '/test/foo/bar', + this.user_id ) this.FileSystemImportManager.addEntity.should.be.calledOnceWith(