Merge pull request #9563 from overleaf/em-tpds-merge-metadata
Return metadata from TPDS update endpoint in web GitOrigin-RevId: 9154be67f7f975807c6e986a5d6fb66013c9a384
This commit is contained in:
@@ -1,16 +1,3 @@
|
||||
/* eslint-disable
|
||||
camelcase,
|
||||
max-len,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const { expect } = require('chai')
|
||||
const ProjectGetter = require('../../../app/src/Features/Project/ProjectGetter.js')
|
||||
const request = require('./helpers/request')
|
||||
@@ -19,19 +6,19 @@ const User = require('./helpers/User')
|
||||
describe('TpdsUpdateTests', function () {
|
||||
beforeEach(function (done) {
|
||||
this.owner = new User()
|
||||
return this.owner.login(error => {
|
||||
if (error != null) {
|
||||
this.owner.login(error => {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
return this.owner.createProject(
|
||||
this.owner.createProject(
|
||||
'test-project',
|
||||
{ template: 'example' },
|
||||
(error, project_id) => {
|
||||
if (error != null) {
|
||||
(error, projectId) => {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
this.project_id = project_id
|
||||
return done()
|
||||
this.projectId = projectId
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -39,10 +26,10 @@ describe('TpdsUpdateTests', function () {
|
||||
|
||||
describe('adding a file', function () {
|
||||
beforeEach(function (done) {
|
||||
return request(
|
||||
request(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/project/${this.project_id}/contents/test.tex`,
|
||||
url: `/project/${this.projectId}/contents/test.tex`,
|
||||
auth: {
|
||||
username: 'sharelatex',
|
||||
password: 'password',
|
||||
@@ -51,34 +38,34 @@ describe('TpdsUpdateTests', function () {
|
||||
body: 'test one two',
|
||||
},
|
||||
(error, response, body) => {
|
||||
if (error != null) {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
expect(response.statusCode).to.equal(200)
|
||||
return done()
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should have added the file', function (done) {
|
||||
return ProjectGetter.getProject(this.project_id, (error, project) => {
|
||||
if (error != null) {
|
||||
ProjectGetter.getProject(this.projectId, (error, project) => {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const projectFolder = project.rootFolder[0]
|
||||
const file = projectFolder.docs.find(e => e.name === 'test.tex')
|
||||
expect(file).to.exist
|
||||
return done()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleting a file', function () {
|
||||
beforeEach(function (done) {
|
||||
return request(
|
||||
request(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: `/project/${this.project_id}/contents/main.tex`,
|
||||
url: `/project/${this.projectId}/contents/main.tex`,
|
||||
auth: {
|
||||
username: 'sharelatex',
|
||||
password: 'password',
|
||||
@@ -86,34 +73,34 @@ describe('TpdsUpdateTests', function () {
|
||||
},
|
||||
},
|
||||
(error, response, body) => {
|
||||
if (error != null) {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
expect(response.statusCode).to.equal(200)
|
||||
return done()
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should have deleted the file', function (done) {
|
||||
return ProjectGetter.getProject(this.project_id, (error, project) => {
|
||||
if (error != null) {
|
||||
ProjectGetter.getProject(this.projectId, (error, project) => {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const projectFolder = project.rootFolder[0]
|
||||
for (const doc of Array.from(projectFolder.docs)) {
|
||||
for (const doc of projectFolder.docs) {
|
||||
if (doc.name === 'main.tex') {
|
||||
throw new Error('expected main.tex to have been deleted')
|
||||
}
|
||||
}
|
||||
return done()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('update a new file', function () {
|
||||
beforeEach(function (done) {
|
||||
return request(
|
||||
request(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/user/${this.owner._id}/update/test-project/other.tex`,
|
||||
@@ -125,24 +112,29 @@ describe('TpdsUpdateTests', function () {
|
||||
body: 'test one two',
|
||||
},
|
||||
(error, response, body) => {
|
||||
if (error != null) {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
expect(response.statusCode).to.equal(200)
|
||||
return done()
|
||||
const json = JSON.parse(response.body)
|
||||
expect(json.status).to.equal('applied')
|
||||
expect(json.entityType).to.equal('doc')
|
||||
expect(json).to.have.property('entityId')
|
||||
expect(json).to.have.property('rev')
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should have added the file', function (done) {
|
||||
return ProjectGetter.getProject(this.project_id, (error, project) => {
|
||||
if (error != null) {
|
||||
ProjectGetter.getProject(this.projectId, (error, project) => {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const projectFolder = project.rootFolder[0]
|
||||
const file = projectFolder.docs.find(e => e.name === 'other.tex')
|
||||
expect(file).to.exist
|
||||
return done()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -151,12 +143,12 @@ describe('TpdsUpdateTests', function () {
|
||||
beforeEach(function (done) {
|
||||
this.owner.request(
|
||||
{
|
||||
url: `/Project/${this.project_id}/archive`,
|
||||
url: `/Project/${this.projectId}/archive`,
|
||||
method: 'post',
|
||||
},
|
||||
(err, response, body) => {
|
||||
expect(err).to.not.exist
|
||||
return request(
|
||||
request(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/user/${this.owner._id}/update/test-project/test.tex`,
|
||||
@@ -168,11 +160,13 @@ describe('TpdsUpdateTests', function () {
|
||||
body: 'test one two',
|
||||
},
|
||||
(error, response, body) => {
|
||||
if (error != null) {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
expect(response.statusCode).to.equal(200)
|
||||
return done()
|
||||
const json = JSON.parse(response.body)
|
||||
expect(json.status).to.equal('rejected')
|
||||
done()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -192,14 +186,14 @@ describe('TpdsUpdateTests', function () {
|
||||
})
|
||||
|
||||
it('should not have added the file', function (done) {
|
||||
return ProjectGetter.getProject(this.project_id, (error, project) => {
|
||||
if (error != null) {
|
||||
ProjectGetter.getProject(this.projectId, (error, project) => {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const projectFolder = project.rootFolder[0]
|
||||
const file = projectFolder.docs.find(e => e.name === 'test.tex')
|
||||
expect(file).to.not.exist
|
||||
return done()
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -77,6 +77,7 @@ describe('ProjectEntityMongoUpdateHandler', function () {
|
||||
getAllEntitiesFromProject: sinon.stub(),
|
||||
}
|
||||
this.ProjectLocator = {
|
||||
findElementByMongoPath: sinon.stub().throws(new Error('not found')),
|
||||
promises: {
|
||||
findElement: sinon.stub().rejects(new Error('not found')),
|
||||
findElementByPath: sinon.stub().rejects(new Error('not found')),
|
||||
@@ -388,6 +389,9 @@ describe('ProjectEntityMongoUpdateHandler', function () {
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves(this.project)
|
||||
this.ProjectLocator.findElementByMongoPath
|
||||
.withArgs(this.project, 'rootFolder.0.fileRefs.0')
|
||||
.returns(newFile)
|
||||
await this.subject.promises.replaceFileWithNew(
|
||||
this.project._id,
|
||||
this.file._id,
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
this.name = options.name
|
||||
this.lines = options.lines
|
||||
this._id = docId
|
||||
this.rev = 0
|
||||
this.rev = options.rev ?? 0
|
||||
}
|
||||
}
|
||||
this.FileModel = class File {
|
||||
@@ -516,14 +516,15 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
describe('adding a doc', function () {
|
||||
beforeEach(function () {
|
||||
this.path = '/path/to/doc'
|
||||
this.rev = 5
|
||||
|
||||
this.newDoc = new this.DocModel({
|
||||
name: this.docName,
|
||||
lines: undefined,
|
||||
_id: docId,
|
||||
rev: 0,
|
||||
rev: this.rev,
|
||||
})
|
||||
this.DocstoreManager.updateDoc.yields(null, false, (this.rev = 5))
|
||||
this.DocstoreManager.updateDoc.yields(null, false, this.rev)
|
||||
this.TpdsUpdateSender.addDoc.yields()
|
||||
this.ProjectEntityMongoUpdateHandler.addDoc.yields(
|
||||
null,
|
||||
@@ -555,18 +556,16 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
docLines: this.docLines.join('\n'),
|
||||
},
|
||||
]
|
||||
this.DocumentUpdaterHandler.updateProjectStructure
|
||||
.calledWith(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
userId,
|
||||
{
|
||||
newDocs,
|
||||
newProject: this.project,
|
||||
},
|
||||
this.source
|
||||
)
|
||||
.should.equal(true)
|
||||
this.DocumentUpdaterHandler.updateProjectStructure.should.have.been.calledWith(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
userId,
|
||||
{
|
||||
newDocs,
|
||||
newProject: this.project,
|
||||
},
|
||||
this.source
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1020,6 +1019,7 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
describe('updating an existing file', function () {
|
||||
beforeEach(function () {
|
||||
this.existingFile = { _id: fileId, name: this.fileName, rev: 1 }
|
||||
this.newFile = { _id: ObjectId(), name: this.fileName, rev: 3 }
|
||||
this.folder = { _id: folderId, fileRefs: [this.existingFile], docs: [] }
|
||||
this.ProjectLocator.findElement.yields(null, this.folder)
|
||||
this.newProject = 'new-project-stub'
|
||||
@@ -1028,7 +1028,8 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
this.existingFile,
|
||||
this.project,
|
||||
{ fileSystem: this.fileSystemPath },
|
||||
this.newProject
|
||||
this.newProject,
|
||||
this.newFile
|
||||
)
|
||||
this.ProjectEntityUpdateHandler.upsertFile(
|
||||
projectId,
|
||||
@@ -1065,8 +1066,8 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
this.TpdsUpdateSender.addFile.should.have.been.calledWith({
|
||||
projectId,
|
||||
projectName: this.project.name,
|
||||
fileId: this.file._id,
|
||||
rev: this.existingFile.rev + 1,
|
||||
fileId: this.newFile._id,
|
||||
rev: this.newFile.rev,
|
||||
path: this.fileSystemPath,
|
||||
folderId,
|
||||
})
|
||||
@@ -1088,7 +1089,7 @@ describe('ProjectEntityUpdateHandler', function () {
|
||||
]
|
||||
const newFiles = [
|
||||
{
|
||||
file: this.file,
|
||||
file: this.newFile,
|
||||
path: this.fileSystemPath,
|
||||
url: this.fileUrl,
|
||||
},
|
||||
|
||||
@@ -557,4 +557,23 @@ describe('ProjectLocator', function () {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('findElementByMongoPath', function () {
|
||||
it('traverses the file tree like Mongo would do', function () {
|
||||
const element = this.locator.findElementByMongoPath(
|
||||
project,
|
||||
'rootFolder.0.folders.1.folders.0.fileRefs.0'
|
||||
)
|
||||
expect(element).to.equal(subSubFile)
|
||||
})
|
||||
|
||||
it('throws an error if no element is found', function () {
|
||||
expect(() =>
|
||||
this.locator.findElementByMongoPath(
|
||||
project,
|
||||
'rootolder.0.folders.0.folders.0.fileRefs.0'
|
||||
)
|
||||
).to.throw
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const { ObjectId } = require('mongodb')
|
||||
const Errors = require('../../../../app/src/Features/Errors/Errors')
|
||||
|
||||
const MODULE_PATH =
|
||||
@@ -8,15 +9,20 @@ const MODULE_PATH =
|
||||
|
||||
describe('TpdsController', function () {
|
||||
beforeEach(function () {
|
||||
this.metadata = {
|
||||
entityId: ObjectId(),
|
||||
entityType: 'doc',
|
||||
rev: 2,
|
||||
}
|
||||
this.TpdsUpdateHandler = {
|
||||
promises: {
|
||||
newUpdate: sinon.stub().resolves(),
|
||||
newUpdate: sinon.stub().resolves(this.metadata),
|
||||
deleteUpdate: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
this.UpdateMerger = {
|
||||
promises: {
|
||||
mergeUpdate: sinon.stub().resolves(),
|
||||
mergeUpdate: sinon.stub().resolves(this.file),
|
||||
deleteUpdate: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
@@ -46,70 +52,65 @@ describe('TpdsController', function () {
|
||||
})
|
||||
|
||||
describe('getting an update', function () {
|
||||
it('should process the update with the update receiver', function (done) {
|
||||
const path = '/projectName/here.txt'
|
||||
const req = {
|
||||
pause() {},
|
||||
params: { 0: path, user_id: this.user_id },
|
||||
session: {
|
||||
destroy() {},
|
||||
},
|
||||
beforeEach(function () {
|
||||
this.projectName = 'projectName'
|
||||
this.path = '/here.txt'
|
||||
this.req = {
|
||||
params: { 0: `${this.projectName}${this.path}`, user_id: this.user_id },
|
||||
headers: {
|
||||
'x-sl-update-source': (this.source = 'dropbox'),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
it('should process the update with the update receiver', function (done) {
|
||||
const res = {
|
||||
sendStatus: () => {
|
||||
json: payload => {
|
||||
expect(payload).to.deep.equal({
|
||||
status: 'applied',
|
||||
entityId: this.metadata.entityId.toString(),
|
||||
entityType: this.metadata.entityType,
|
||||
rev: this.metadata.rev,
|
||||
})
|
||||
this.TpdsUpdateHandler.promises.newUpdate
|
||||
.calledWith(
|
||||
this.user_id,
|
||||
'projectName',
|
||||
'/here.txt',
|
||||
req,
|
||||
this.projectName,
|
||||
this.path,
|
||||
this.req,
|
||||
this.source
|
||||
)
|
||||
.should.equal(true)
|
||||
done()
|
||||
},
|
||||
}
|
||||
this.TpdsController.mergeUpdate(req, res)
|
||||
this.TpdsController.mergeUpdate(this.req, res)
|
||||
})
|
||||
|
||||
it('should indicate in the response when the update was rejected', function (done) {
|
||||
this.TpdsUpdateHandler.promises.newUpdate.resolves(null)
|
||||
const res = {
|
||||
json: payload => {
|
||||
expect(payload).to.deep.equal({ status: 'rejected' })
|
||||
done()
|
||||
},
|
||||
}
|
||||
this.TpdsController.mergeUpdate(this.req, res)
|
||||
})
|
||||
|
||||
it('should return a 500 error when the update receiver fails', function (done) {
|
||||
const path = '/projectName/here.txt'
|
||||
const req = {
|
||||
pause() {},
|
||||
params: { 0: path, user_id: this.user_id },
|
||||
session: {
|
||||
destroy() {},
|
||||
},
|
||||
headers: {
|
||||
'x-sl-update-source': (this.source = 'dropbox'),
|
||||
},
|
||||
}
|
||||
this.TpdsUpdateHandler.promises.newUpdate.rejects(new Error())
|
||||
const res = {
|
||||
sendStatus: sinon.stub(),
|
||||
json: sinon.stub(),
|
||||
}
|
||||
this.TpdsController.mergeUpdate(req, res, err => {
|
||||
this.TpdsController.mergeUpdate(this.req, res, err => {
|
||||
expect(err).to.exist
|
||||
expect(res.sendStatus).not.to.have.been.called
|
||||
expect(res.json).not.to.have.been.called
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should return a 400 error when the project is too big', function (done) {
|
||||
const path = '/projectName/here.txt'
|
||||
const req = {
|
||||
pause() {},
|
||||
params: { 0: path, user_id: this.user_id, projectName: 'projectName' },
|
||||
session: {
|
||||
destroy() {},
|
||||
},
|
||||
headers: {
|
||||
'x-sl-update-source': (this.source = 'dropbox'),
|
||||
},
|
||||
}
|
||||
this.TpdsUpdateHandler.promises.newUpdate.rejects({
|
||||
message: 'project_has_too_many_files',
|
||||
})
|
||||
@@ -122,21 +123,10 @@ describe('TpdsController', function () {
|
||||
done()
|
||||
},
|
||||
}
|
||||
this.TpdsController.mergeUpdate(req, res)
|
||||
this.TpdsController.mergeUpdate(this.req, res)
|
||||
})
|
||||
|
||||
it('should return a 429 error when the update receiver fails due to too many requests error', function (done) {
|
||||
const path = '/projectName/here.txt'
|
||||
const req = {
|
||||
pause() {},
|
||||
params: { 0: path, user_id: this.user_id },
|
||||
session: {
|
||||
destroy() {},
|
||||
},
|
||||
headers: {
|
||||
'x-sl-update-source': (this.source = 'dropbox'),
|
||||
},
|
||||
}
|
||||
this.TpdsUpdateHandler.promises.newUpdate.rejects(
|
||||
new Errors.TooManyRequestsError('project on cooldown')
|
||||
)
|
||||
@@ -146,7 +136,7 @@ describe('TpdsController', function () {
|
||||
done()
|
||||
},
|
||||
}
|
||||
this.TpdsController.mergeUpdate(req, res)
|
||||
this.TpdsController.mergeUpdate(this.req, res)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
const BufferedStream = require('bufferedstream')
|
||||
const { ObjectId } = require('mongodb')
|
||||
|
||||
const MODULE_PATH =
|
||||
'../../../../app/src/Features/ThirdPartyDataStore/UpdateMerger.js'
|
||||
@@ -38,11 +39,21 @@ describe('UpdateMerger :', function () {
|
||||
readFile: sinon.stub().withArgs(this.fsPath).resolves(this.fileContents),
|
||||
}
|
||||
|
||||
this.doc = {
|
||||
_id: ObjectId(),
|
||||
rev: 2,
|
||||
}
|
||||
|
||||
this.file = {
|
||||
_id: ObjectId(),
|
||||
rev: 6,
|
||||
}
|
||||
|
||||
this.EditorController = {
|
||||
promises: {
|
||||
deleteEntityWithPath: sinon.stub().resolves(),
|
||||
upsertDocWithPath: sinon.stub().resolves(),
|
||||
upsertFileWithPath: sinon.stub().resolves(),
|
||||
upsertDocWithPath: sinon.stub().resolves(this.doc),
|
||||
upsertFileWithPath: sinon.stub().resolves(this.file),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -239,17 +250,6 @@ describe('UpdateMerger :', function () {
|
||||
expect(this.FileTypeManager.promises.getType).to.have.been.called
|
||||
})
|
||||
|
||||
it('should delete the existing doc', function () {
|
||||
expect(
|
||||
this.EditorController.promises.deleteEntityWithPath
|
||||
).to.have.been.calledWith(
|
||||
this.projectId,
|
||||
this.existingDocPath,
|
||||
this.source,
|
||||
this.userId
|
||||
)
|
||||
})
|
||||
|
||||
it('should process update as file', function () {
|
||||
expect(
|
||||
this.EditorController.promises.upsertFileWithPath
|
||||
|
||||
Reference in New Issue
Block a user