diff --git a/services/web/app/src/Features/History/RestoreManager.js b/services/web/app/src/Features/History/RestoreManager.js index 16ef2024f6..316de88d82 100644 --- a/services/web/app/src/Features/History/RestoreManager.js +++ b/services/web/app/src/Features/History/RestoreManager.js @@ -79,6 +79,7 @@ const RestoreManager = { ) { const project = await ProjectGetter.promises.getProject(projectId, { overleaf: true, + rootDoc_id: true, }) if (!project?.overleaf?.history?.rangesSupportEnabled) { throw new OError('project does not have ranges support', { projectId }) @@ -125,6 +126,7 @@ const RestoreManager = { pathname ) + let hadDeletedRootFile = false if (file) { if (file.type !== 'doc' && file.type !== 'file') { throw new OError('unexpected file type', { type: file.type }) @@ -140,6 +142,11 @@ const RestoreManager = { origin, userId ) + + if (file.element._id.toString() === project.rootDoc_id.toString()) { + hadDeletedRootFile = true + } + threadIds.delete(file.element._id.toString()) } @@ -274,6 +281,11 @@ const RestoreManager = { origin, userId ) + + if (hadDeletedRootFile) { + await EditorController.promises.setRootDoc(projectId, _id) + } + // For revertProject: The next doc that gets reverted will need to duplicate all the threads seen here. threadIds.set( _id.toString(), diff --git a/services/web/test/unit/src/History/RestoreManagerTests.js b/services/web/test/unit/src/History/RestoreManagerTests.js index f76ba506ad..0f6ded797b 100644 --- a/services/web/test/unit/src/History/RestoreManagerTests.js +++ b/services/web/test/unit/src/History/RestoreManagerTests.js @@ -225,7 +225,10 @@ describe('RestoreManager', function () { this.ProjectGetter.promises.getProject = sinon.stub() this.ProjectGetter.promises.getProject .withArgs(this.project_id) - .resolves({ overleaf: { history: { rangesSupportEnabled: true } } }) + .resolves({ + overleaf: { history: { rangesSupportEnabled: true } }, + rootDoc_id: 'root-doc-id', + }) this.RestoreManager.promises._writeFileVersionToDisk = sinon .stub() .resolves((this.fsPath = '/tmp/path/on/disk')) @@ -405,6 +408,13 @@ describe('RestoreManager', function () { describe('with an existing file in the current project', function () { beforeEach(async function () { + this.ProjectGetter.promises.getProject = sinon.stub() + this.ProjectGetter.promises.getProject + .withArgs(this.project_id) + .resolves({ + overleaf: { history: { rangesSupportEnabled: true } }, + rootDoc_id: 'root-doc-id', + }) this.ProjectLocator.promises.findElementByPath = sinon .stub() .resolves({ type: 'file', element: { _id: 'mock-file-id' } }) @@ -438,6 +448,13 @@ describe('RestoreManager', function () { describe('with an existing document in the current project', function () { beforeEach(async function () { + this.ProjectGetter.promises.getProject = sinon.stub() + this.ProjectGetter.promises.getProject + .withArgs(this.project_id) + .resolves({ + overleaf: { history: { rangesSupportEnabled: true } }, + rootDoc_id: 'root-doc-id', + }) this.ProjectLocator.promises.findElementByPath = sinon .stub() .resolves({ type: 'doc', element: { _id: 'mock-file-id' } }) @@ -512,6 +529,13 @@ describe('RestoreManager', function () { describe('with comments in same doc', function () { // copy of the above, addition: inject and later inspect threadIds set beforeEach(async function () { + this.ProjectGetter.promises.getProject = sinon.stub() + this.ProjectGetter.promises.getProject + .withArgs(this.project_id) + .resolves({ + overleaf: { history: { rangesSupportEnabled: true } }, + rootDoc_id: 'root-doc-id', + }) this.ProjectLocator.promises.findElementByPath = sinon .stub() .resolves({ type: 'doc', element: { _id: 'mock-file-id' } }) @@ -595,6 +619,13 @@ describe('RestoreManager', function () { describe('with remapped comments during revertProject', function () { // copy of the above, addition: inject and later inspect threadIds set beforeEach(async function () { + this.ProjectGetter.promises.getProject = sinon.stub() + this.ProjectGetter.promises.getProject + .withArgs(this.project_id) + .resolves({ + overleaf: { history: { rangesSupportEnabled: true } }, + rootDoc_id: 'root-doc-id', + }) this.ProjectLocator.promises.findElementByPath = sinon .stub() .resolves({ type: 'doc', element: { _id: 'mock-file-id' } }) @@ -645,6 +676,79 @@ describe('RestoreManager', function () { ) }) }) + + describe('when restored file has the same id as root doc', function () { + beforeEach(async function () { + this.ProjectGetter.promises.getProject = sinon.stub() + this.ProjectGetter.promises.getProject + .withArgs(this.project_id) + .resolves({ + overleaf: { history: { rangesSupportEnabled: true } }, + rootDoc_id: 'root-doc-id', + }) + this.ProjectLocator.promises.findElementByPath = sinon + .stub() + .resolves({ type: 'doc', element: { _id: 'root-doc-id' } }) + this.EditorController.promises.deleteEntity = sinon.stub().resolves() + this.EditorController.promises.addDocWithRanges = sinon + .stub() + .resolves((this.addedFile = { _id: 'new-doc-id', type: 'doc' })) + this.EditorController.promises.setRootDoc = sinon.stub().resolves() + + this.data = await this.RestoreManager.promises.revertFile( + this.user_id, + this.project_id, + this.version, + this.pathname + ) + }) + + it('should delete the existing root document', async function () { + expect( + this.EditorController.promises.deleteEntity + ).to.have.been.calledWith( + this.project_id, + 'root-doc-id', + 'doc', + { + kind: 'file-restore', + path: this.pathname, + version: this.version, + timestamp: new Date(this.endTs).toISOString(), + }, + this.user_id + ) + }) + + it('should import the file', function () { + expect( + this.EditorController.promises.addDocWithRanges + ).to.have.been.calledWith( + this.project_id, + this.folder_id, + 'foo.tex', + ['foo', 'bar', 'baz'], + { changes: this.tracked_changes, comments: this.remappedComments }, + { + kind: 'file-restore', + path: this.pathname, + version: this.version, + timestamp: new Date(this.endTs).toISOString(), + } + ) + }) + + it('should return the created entity with root doc id', function () { + expect(this.data).to.deep.equal(this.addedFile) + expect(this.data._id).to.equal('new-doc-id') + }) + + it('should set the restored document as the new root doc', function () { + expect( + this.EditorController.promises.setRootDoc + ).to.have.been.calledWith(this.project_id, this.addedFile._id) + }) + }) }) describe('reverting a file or document with metadata', function () {