Merge pull request #4761 from overleaf/em-peek-doc

Do not unarchive docs when resyncing project history

GitOrigin-RevId: c7df75789c01e6c85b464a9b94b14654d8568407
This commit is contained in:
Eric Mc Sween
2021-08-20 08:03:50 +00:00
committed by Copybot
parent be72e6ab19
commit 9f4541c266
8 changed files with 849 additions and 1040 deletions
@@ -1,303 +1,277 @@
/* eslint-disable
camelcase,
node/handle-callback-err,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* 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 { promisify } = require('util')
const { promisifyMultiResult } = require('../../util/promises')
const request = require('request').defaults({ jar: false })
const OError = require('@overleaf/o-error')
const logger = require('logger-sharelatex')
const settings = require('@overleaf/settings')
const Errors = require('../Errors/Errors')
const { promisifyAll } = require('../../util/promises')
const TIMEOUT = 30 * 1000 // request timeout
const DocstoreManager = {
deleteDoc(project_id, doc_id, name, deletedAt, callback) {
if (callback == null) {
callback = function (error) {}
function deleteDoc(projectId, docId, name, deletedAt, callback) {
const url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}`
const docMetaData = { deleted: true, deletedAt, name }
const options = { url, json: docMetaData, timeout: TIMEOUT }
request.patch(options, (error, res) => {
if (error) {
return callback(error)
}
const url = `${settings.apis.docstore.url}/project/${project_id}/doc/${doc_id}`
const docMetaData = { deleted: true, deletedAt, name }
const options = { url, json: docMetaData, timeout: TIMEOUT }
request.patch(options, function (error, res) {
if (error != null) {
if (res.statusCode >= 200 && res.statusCode < 300) {
callback(null)
} else if (res.statusCode === 404) {
error = new Errors.NotFoundError({
message: 'tried to delete doc not in docstore',
info: {
projectId,
docId,
},
})
callback(error) // maybe suppress the error when delete doc which is not present?
} else {
error = new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{
projectId,
docId,
}
)
callback(error)
}
})
}
function getAllDocs(projectId, callback) {
const url = `${settings.apis.docstore.url}/project/${projectId}/doc`
request.get(
{
url,
timeout: TIMEOUT,
json: true,
},
(error, res, docs) => {
if (error) {
return callback(error)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
return callback(null)
} else if (res.statusCode === 404) {
error = new Errors.NotFoundError({
message: 'tried to delete doc not in docstore',
info: {
project_id,
doc_id,
},
})
return callback(error) // maybe suppress the error when delete doc which is not present?
callback(null, docs)
} else {
error = new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{
project_id,
doc_id,
}
)
return callback(error)
}
})
},
getAllDocs(project_id, callback) {
if (callback == null) {
callback = function (error) {}
}
const url = `${settings.apis.docstore.url}/project/${project_id}/doc`
return request.get(
{
url,
timeout: TIMEOUT,
json: true,
},
function (error, res, docs) {
if (error != null) {
return callback(error)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
return callback(null, docs)
} else {
error = new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{ project_id }
)
return callback(error)
}
}
)
},
getAllDeletedDocs(project_id, callback) {
const url = `${settings.apis.docstore.url}/project/${project_id}/doc-deleted`
request.get(
{ url, timeout: TIMEOUT, json: true },
function (error, res, docs) {
if (error) {
callback(
OError.tag(error, 'could not get deleted docs from docstore')
)
} else if (res.statusCode === 200) {
callback(null, docs)
} else {
callback(
new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{ project_id }
)
)
}
}
)
},
getAllRanges(project_id, callback) {
if (callback == null) {
callback = function (error) {}
}
const url = `${settings.apis.docstore.url}/project/${project_id}/ranges`
return request.get(
{
url,
timeout: TIMEOUT,
json: true,
},
function (error, res, docs) {
if (error != null) {
return callback(error)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
return callback(null, docs)
} else {
error = new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{ project_id }
)
return callback(error)
}
}
)
},
getDoc(project_id, doc_id, options, callback) {
if (options == null) {
options = {}
}
if (callback == null) {
callback = function (error, lines, rev, version) {}
}
if (typeof options === 'function') {
callback = options
options = {}
}
let url = `${settings.apis.docstore.url}/project/${project_id}/doc/${doc_id}`
if (options.include_deleted) {
url += '?include_deleted=true'
}
return request.get(
{
url,
timeout: TIMEOUT,
json: true,
},
function (error, res, doc) {
if (error != null) {
return callback(error)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
logger.log(
{ doc_id, project_id, version: doc.version, rev: doc.rev },
'got doc from docstore api'
)
return callback(null, doc.lines, doc.rev, doc.version, doc.ranges)
} else if (res.statusCode === 404) {
error = new Errors.NotFoundError({
message: 'doc not found in docstore',
info: {
project_id,
doc_id,
},
})
return callback(error)
} else {
error = new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{
project_id,
doc_id,
}
)
return callback(error)
}
}
)
},
isDocDeleted(project_id, doc_id, callback) {
const url = `${settings.apis.docstore.url}/project/${project_id}/doc/${doc_id}/deleted`
request.get(
{ url, timeout: TIMEOUT, json: true },
function (err, res, body) {
if (err) {
callback(err)
} else if (res.statusCode === 200) {
callback(null, body.deleted)
} else if (res.statusCode === 404) {
callback(
new Errors.NotFoundError({
message: 'doc does not exist in project',
info: { project_id, doc_id },
})
)
} else {
callback(
new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{ project_id, doc_id }
)
)
}
}
)
},
updateDoc(project_id, doc_id, lines, version, ranges, callback) {
if (callback == null) {
callback = function (error, modified, rev) {}
}
const url = `${settings.apis.docstore.url}/project/${project_id}/doc/${doc_id}`
return request.post(
{
url,
timeout: TIMEOUT,
json: {
lines,
version,
ranges,
},
},
function (error, res, result) {
if (error != null) {
return callback(error)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
logger.log(
{ project_id, doc_id },
'update doc in docstore url finished'
)
return callback(null, result.modified, result.rev)
} else {
error = new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{ project_id, doc_id }
)
return callback(error)
}
}
)
},
archiveProject(project_id, callback) {
DocstoreManager._operateOnProject(project_id, 'archive', callback)
},
unarchiveProject(project_id, callback) {
DocstoreManager._operateOnProject(project_id, 'unarchive', callback)
},
destroyProject(project_id, callback) {
DocstoreManager._operateOnProject(project_id, 'destroy', callback)
},
_operateOnProject(project_id, method, callback) {
const url = `${settings.apis.docstore.url}/project/${project_id}/${method}`
logger.log({ project_id }, `calling ${method} for project in docstore`)
// use default timeout for archiving/unarchiving/destroying
request.post(url, function (err, res, docs) {
if (err != null) {
OError.tag(err, `error calling ${method} project in docstore`, {
project_id,
})
return callback(err)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
callback()
} else {
const error = new Error(
`docstore api responded with non-success code: ${res.statusCode}`
)
logger.warn(
{ err: error, project_id },
`error calling ${method} project in docstore`
{ projectId }
)
callback(error)
}
})
},
}
)
}
module.exports = DocstoreManager
module.exports.promises = promisifyAll(DocstoreManager, {
multiResult: {
getDoc: ['lines', 'rev', 'version', 'ranges'],
updateDoc: ['modified', 'rev'],
function getAllDeletedDocs(projectId, callback) {
const url = `${settings.apis.docstore.url}/project/${projectId}/doc-deleted`
request.get({ url, timeout: TIMEOUT, json: true }, (error, res, docs) => {
if (error) {
callback(OError.tag(error, 'could not get deleted docs from docstore'))
} else if (res.statusCode === 200) {
callback(null, docs)
} else {
callback(
new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{ projectId }
)
)
}
})
}
function getAllRanges(projectId, callback) {
const url = `${settings.apis.docstore.url}/project/${projectId}/ranges`
request.get(
{
url,
timeout: TIMEOUT,
json: true,
},
(error, res, docs) => {
if (error) {
return callback(error)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
callback(null, docs)
} else {
error = new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{ projectId }
)
callback(error)
}
}
)
}
function getDoc(projectId, docId, options, callback) {
if (options == null) {
options = {}
}
if (typeof options === 'function') {
callback = options
options = {}
}
const requestParams = { timeout: TIMEOUT, json: true }
if (options.peek) {
requestParams.url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}/peek`
} else {
requestParams.url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}`
}
if (options.include_deleted) {
requestParams.qs = { include_deleted: 'true' }
}
request.get(requestParams, (error, res, doc) => {
if (error) {
return callback(error)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
logger.log(
{ docId, projectId, version: doc.version, rev: doc.rev },
'got doc from docstore api'
)
callback(null, doc.lines, doc.rev, doc.version, doc.ranges)
} else if (res.statusCode === 404) {
error = new Errors.NotFoundError({
message: 'doc not found in docstore',
info: {
projectId,
docId,
},
})
callback(error)
} else {
error = new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{
projectId,
docId,
}
)
callback(error)
}
})
}
function isDocDeleted(projectId, docId, callback) {
const url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}/deleted`
request.get({ url, timeout: TIMEOUT, json: true }, (err, res, body) => {
if (err) {
callback(err)
} else if (res.statusCode === 200) {
callback(null, body.deleted)
} else if (res.statusCode === 404) {
callback(
new Errors.NotFoundError({
message: 'doc does not exist in project',
info: { projectId, docId },
})
)
} else {
callback(
new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{ projectId, docId }
)
)
}
})
}
function updateDoc(projectId, docId, lines, version, ranges, callback) {
const url = `${settings.apis.docstore.url}/project/${projectId}/doc/${docId}`
request.post(
{
url,
timeout: TIMEOUT,
json: {
lines,
version,
ranges,
},
},
(error, res, result) => {
if (error) {
return callback(error)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
logger.log({ projectId, docId }, 'update doc in docstore url finished')
callback(null, result.modified, result.rev)
} else {
error = new OError(
`docstore api responded with non-success code: ${res.statusCode}`,
{ projectId, docId }
)
callback(error)
}
}
)
}
function archiveProject(projectId, callback) {
_operateOnProject(projectId, 'archive', callback)
}
function unarchiveProject(projectId, callback) {
_operateOnProject(projectId, 'unarchive', callback)
}
function destroyProject(projectId, callback) {
_operateOnProject(projectId, 'destroy', callback)
}
function _operateOnProject(projectId, method, callback) {
const url = `${settings.apis.docstore.url}/project/${projectId}/${method}`
logger.log({ projectId }, `calling ${method} for project in docstore`)
// use default timeout for archiving/unarchiving/destroying
request.post(url, (err, res, docs) => {
if (err) {
OError.tag(err, `error calling ${method} project in docstore`, {
projectId,
})
return callback(err)
}
if (res.statusCode >= 200 && res.statusCode < 300) {
callback()
} else {
const error = new Error(
`docstore api responded with non-success code: ${res.statusCode}`
)
logger.warn(
{ err: error, projectId },
`error calling ${method} project in docstore`
)
callback(error)
}
})
}
module.exports = {
deleteDoc,
getAllDocs,
getAllDeletedDocs,
getAllRanges,
getDoc,
isDocDeleted,
updateDoc,
archiveProject,
unarchiveProject,
destroyProject,
promises: {
deleteDoc: promisify(deleteDoc),
getAllDocs: promisify(getAllDocs),
getAllDeletedDocs: promisify(getAllDeletedDocs),
getAllRanges: promisify(getAllRanges),
getDoc: promisifyMultiResult(getDoc, ['lines', 'rev', 'version', 'ranges']),
isDocDeleted: promisify(isDocDeleted),
updateDoc: promisifyMultiResult(updateDoc, ['modified', 'rev']),
archiveProject: promisify(archiveProject),
unarchiveProject: promisify(unarchiveProject),
destroyProject: promisify(destroyProject),
},
})
}
@@ -1,17 +1,3 @@
/* eslint-disable
camelcase,
node/handle-callback-err,
max-len,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS103: Rewrite code to no longer use __guard__
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const ProjectGetter = require('../Project/ProjectGetter')
const OError = require('@overleaf/o-error')
const ProjectLocator = require('../Project/ProjectLocator')
@@ -20,122 +6,109 @@ const ProjectEntityUpdateHandler = require('../Project/ProjectEntityUpdateHandle
const logger = require('logger-sharelatex')
const _ = require('lodash')
module.exports = {
getDocument(req, res, next) {
if (next == null) {
next = function (error) {}
}
const project_id = req.params.Project_id
const { doc_id } = req.params
const plain =
__guard__(req != null ? req.query : undefined, x => x.plain) === 'true'
return ProjectGetter.getProject(
project_id,
{ rootFolder: true, overleaf: true },
function (error, project) {
if (error != null) {
return next(error)
}
if (project == null) {
return res.sendStatus(404)
}
return ProjectLocator.findElement(
{ project, element_id: doc_id, type: 'doc' },
function (error, doc, path) {
if (error != null) {
OError.tag(error, 'error finding element for getDocument', {
doc_id,
project_id,
})
return next(error)
}
return ProjectEntityHandler.getDoc(
project_id,
doc_id,
function (error, lines, rev, version, ranges) {
if (error != null) {
OError.tag(
error,
'error finding doc contents for getDocument',
{
doc_id,
project_id,
}
)
return next(error)
}
if (plain) {
res.type('text/plain')
return res.send(lines.join('\n'))
} else {
const projectHistoryId = _.get(project, 'overleaf.history.id')
const projectHistoryDisplay = _.get(
project,
'overleaf.history.display'
)
const sendToBothHistorySystems = _.get(
project,
'overleaf.history.allowDowngrade'
)
// if project has been switched but has 'allowDowngrade' set
// then leave projectHistoryType undefined to (temporarily)
// continue sending updates to both SL and full project history
const projectHistoryType =
projectHistoryDisplay && !sendToBothHistorySystems
? 'project-history'
: undefined // for backwards compatibility, don't send anything if the project is still on track-changes
return res.json({
lines,
version,
ranges,
pathname: path.fileSystem,
projectHistoryId,
projectHistoryType,
})
}
}
)
function getDocument(req, res, next) {
const { Project_id: projectId, doc_id: docId } = req.params
const plain = req.query.plain === 'true'
const peek = req.query.peek === 'true'
ProjectGetter.getProject(
projectId,
{ rootFolder: true, overleaf: true },
(error, project) => {
if (error) {
return next(error)
}
if (!project) {
return res.sendStatus(404)
}
ProjectLocator.findElement(
{ project, element_id: docId, type: 'doc' },
(error, doc, path) => {
if (error) {
OError.tag(error, 'error finding element for getDocument', {
docId,
projectId,
})
return next(error)
}
)
}
)
},
setDocument(req, res, next) {
if (next == null) {
next = function (error) {}
}
const project_id = req.params.Project_id
const { doc_id } = req.params
const { lines, version, ranges, lastUpdatedAt, lastUpdatedBy } = req.body
return ProjectEntityUpdateHandler.updateDocLines(
project_id,
doc_id,
lines,
version,
ranges,
lastUpdatedAt,
lastUpdatedBy,
function (error) {
if (error != null) {
OError.tag(error, 'error finding element for getDocument', {
doc_id,
project_id,
})
return next(error)
ProjectEntityHandler.getDoc(
projectId,
docId,
{ peek },
(error, lines, rev, version, ranges) => {
if (error) {
OError.tag(
error,
'error finding doc contents for getDocument',
{
docId,
projectId,
}
)
return next(error)
}
if (plain) {
res.type('text/plain')
res.send(lines.join('\n'))
} else {
const projectHistoryId = _.get(project, 'overleaf.history.id')
const projectHistoryDisplay = _.get(
project,
'overleaf.history.display'
)
const sendToBothHistorySystems = _.get(
project,
'overleaf.history.allowDowngrade'
)
// if project has been switched but has 'allowDowngrade' set
// then leave projectHistoryType undefined to (temporarily)
// continue sending updates to both SL and full project history
const projectHistoryType =
projectHistoryDisplay && !sendToBothHistorySystems
? 'project-history'
: undefined // for backwards compatibility, don't send anything if the project is still on track-changes
res.json({
lines,
version,
ranges,
pathname: path.fileSystem,
projectHistoryId,
projectHistoryType,
})
}
}
)
}
logger.log(
{ doc_id, project_id },
'finished receiving set document request from api (docupdater)'
)
return res.sendStatus(200)
}
)
},
)
}
)
}
function __guard__(value, transform) {
return typeof value !== 'undefined' && value !== null
? transform(value)
: undefined
function setDocument(req, res, next) {
const { Project_id: projectId, doc_id: docId } = req.params
const { lines, version, ranges, lastUpdatedAt, lastUpdatedBy } = req.body
ProjectEntityUpdateHandler.updateDocLines(
projectId,
docId,
lines,
version,
ranges,
lastUpdatedAt,
lastUpdatedBy,
error => {
if (error) {
OError.tag(error, 'error finding element for getDocument', {
docId,
projectId,
})
return next(error)
}
logger.log(
{ docId, projectId },
'finished receiving set document request from api (docupdater)'
)
res.sendStatus(200)
}
)
}
module.exports = { getDocument, setDocument }
@@ -1,14 +1,3 @@
/* eslint-disable
max-len,
no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const sinon = require('sinon')
const modulePath = '../../../../app/src/Features/Docstore/DocstoreManager'
const SandboxedModule = require('sandboxed-module')
@@ -37,7 +26,7 @@ describe('DocstoreManager', function () {
this.project_id = 'project-id-123'
this.doc_id = 'doc-id-123'
return (this.callback = sinon.stub())
this.callback = sinon.stub()
})
describe('deleteDoc', function () {
@@ -54,7 +43,7 @@ describe('DocstoreManager', function () {
this.request.patch = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 }, '')
return this.DocstoreManager.deleteDoc(
this.DocstoreManager.deleteDoc(
this.project_id,
this.doc_id,
'wombat.tex',
@@ -64,7 +53,7 @@ describe('DocstoreManager', function () {
})
it('should delete the doc in the docstore api', function () {
return this.request.patch
this.request.patch
.calledWith({
url: `${this.settings.apis.docstore.url}/project/${this.project_id}/doc/${this.doc_id}`,
json: { deleted: true, deletedAt: new Date(), name: 'wombat.tex' },
@@ -74,7 +63,7 @@ describe('DocstoreManager', function () {
})
it('should call the callback without an error', function () {
return this.callback.calledWith(null).should.equal(true)
this.callback.calledWith(null).should.equal(true)
})
})
@@ -83,7 +72,7 @@ describe('DocstoreManager', function () {
this.request.patch = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
return this.DocstoreManager.deleteDoc(
this.DocstoreManager.deleteDoc(
this.project_id,
this.doc_id,
'main.tex',
@@ -93,7 +82,7 @@ describe('DocstoreManager', function () {
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -113,7 +102,7 @@ describe('DocstoreManager', function () {
this.request.patch = sinon
.stub()
.callsArgWith(1, null, { statusCode: 404 }, '')
return this.DocstoreManager.deleteDoc(
this.DocstoreManager.deleteDoc(
this.project_id,
this.doc_id,
'main.tex',
@@ -145,7 +134,7 @@ describe('DocstoreManager', function () {
this.rev = 5
this.version = 42
this.ranges = { mock: 'ranges' }
return (this.modified = true)
this.modified = true
})
describe('with a successful response code', function () {
@@ -158,7 +147,7 @@ describe('DocstoreManager', function () {
{ statusCode: 204 },
{ modified: this.modified, rev: this.rev }
)
return this.DocstoreManager.updateDoc(
this.DocstoreManager.updateDoc(
this.project_id,
this.doc_id,
this.lines,
@@ -169,7 +158,7 @@ describe('DocstoreManager', function () {
})
it('should update the doc in the docstore api', function () {
return this.request.post
this.request.post
.calledWith({
url: `${this.settings.apis.docstore.url}/project/${this.project_id}/doc/${this.doc_id}`,
timeout: 30 * 1000,
@@ -183,7 +172,7 @@ describe('DocstoreManager', function () {
})
it('should call the callback with the modified status and revision', function () {
return this.callback
this.callback
.calledWith(null, this.modified, this.rev)
.should.equal(true)
})
@@ -194,7 +183,7 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
return this.DocstoreManager.updateDoc(
this.DocstoreManager.updateDoc(
this.project_id,
this.doc_id,
this.lines,
@@ -205,7 +194,7 @@ describe('DocstoreManager', function () {
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -223,12 +212,12 @@ describe('DocstoreManager', function () {
describe('getDoc', function () {
beforeEach(function () {
return (this.doc = {
this.doc = {
lines: (this.lines = ['mock', 'doc', 'lines']),
rev: (this.rev = 5),
version: (this.version = 42),
ranges: (this.ranges = { mock: 'ranges' }),
})
}
})
describe('with a successful response code', function () {
@@ -236,25 +225,19 @@ describe('DocstoreManager', function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 }, this.doc)
return this.DocstoreManager.getDoc(
this.project_id,
this.doc_id,
this.callback
)
this.DocstoreManager.getDoc(this.project_id, this.doc_id, this.callback)
})
it('should get the doc from the docstore api', function () {
return this.request.get
.calledWith({
url: `${this.settings.apis.docstore.url}/project/${this.project_id}/doc/${this.doc_id}`,
timeout: 30 * 1000,
json: true,
})
.should.equal(true)
this.request.get.should.have.been.calledWith({
url: `${this.settings.apis.docstore.url}/project/${this.project_id}/doc/${this.doc_id}`,
timeout: 30 * 1000,
json: true,
})
})
it('should call the callback with the lines, version and rev', function () {
return this.callback
this.callback
.calledWith(null, this.lines, this.rev, this.version, this.ranges)
.should.equal(true)
})
@@ -265,15 +248,11 @@ describe('DocstoreManager', function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
return this.DocstoreManager.getDoc(
this.project_id,
this.doc_id,
this.callback
)
this.DocstoreManager.getDoc(this.project_id, this.doc_id, this.callback)
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -293,7 +272,7 @@ describe('DocstoreManager', function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 }, this.doc)
return this.DocstoreManager.getDoc(
this.DocstoreManager.getDoc(
this.project_id,
this.doc_id,
{ include_deleted: true },
@@ -302,36 +281,53 @@ describe('DocstoreManager', function () {
})
it('should get the doc from the docstore api (including deleted)', function () {
return this.request.get
.calledWith({
url: `${this.settings.apis.docstore.url}/project/${this.project_id}/doc/${this.doc_id}?include_deleted=true`,
timeout: 30 * 1000,
json: true,
})
.should.equal(true)
this.request.get.should.have.been.calledWith({
url: `${this.settings.apis.docstore.url}/project/${this.project_id}/doc/${this.doc_id}`,
qs: { include_deleted: 'true' },
timeout: 30 * 1000,
json: true,
})
})
it('should call the callback with the lines, version and rev', function () {
return this.callback
this.callback
.calledWith(null, this.lines, this.rev, this.version, this.ranges)
.should.equal(true)
})
})
describe('with peek=true', function () {
beforeEach(function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 }, this.doc)
this.DocstoreManager.getDoc(
this.project_id,
this.doc_id,
{ peek: true },
this.callback
)
})
it('should call the docstore peek url', function () {
this.request.get.should.have.been.calledWith({
url: `${this.settings.apis.docstore.url}/project/${this.project_id}/doc/${this.doc_id}/peek`,
timeout: 30 * 1000,
json: true,
})
})
})
describe('with a missing (404) response code', function () {
beforeEach(function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 404 }, '')
return this.DocstoreManager.getDoc(
this.project_id,
this.doc_id,
this.callback
)
this.DocstoreManager.getDoc(this.project_id, this.doc_id, this.callback)
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Errors.NotFoundError)
@@ -353,11 +349,11 @@ describe('DocstoreManager', function () {
{ statusCode: 204 },
(this.docs = [{ _id: 'mock-doc-id' }])
)
return this.DocstoreManager.getAllDocs(this.project_id, this.callback)
this.DocstoreManager.getAllDocs(this.project_id, this.callback)
})
it('should get all the project docs in the docstore api', function () {
return this.request.get
this.request.get
.calledWith({
url: `${this.settings.apis.docstore.url}/project/${this.project_id}/doc`,
timeout: 30 * 1000,
@@ -367,7 +363,7 @@ describe('DocstoreManager', function () {
})
it('should call the callback with the docs', function () {
return this.callback.calledWith(null, this.docs).should.equal(true)
this.callback.calledWith(null, this.docs).should.equal(true)
})
})
@@ -376,11 +372,11 @@ describe('DocstoreManager', function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
return this.DocstoreManager.getAllDocs(this.project_id, this.callback)
this.DocstoreManager.getAllDocs(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -473,11 +469,11 @@ describe('DocstoreManager', function () {
{ statusCode: 204 },
(this.docs = [{ _id: 'mock-doc-id', ranges: 'mock-ranges' }])
)
return this.DocstoreManager.getAllRanges(this.project_id, this.callback)
this.DocstoreManager.getAllRanges(this.project_id, this.callback)
})
it('should get all the project doc ranges in the docstore api', function () {
return this.request.get
this.request.get
.calledWith({
url: `${this.settings.apis.docstore.url}/project/${this.project_id}/ranges`,
timeout: 30 * 1000,
@@ -487,7 +483,7 @@ describe('DocstoreManager', function () {
})
it('should call the callback with the docs', function () {
return this.callback.calledWith(null, this.docs).should.equal(true)
this.callback.calledWith(null, this.docs).should.equal(true)
})
})
@@ -496,11 +492,11 @@ describe('DocstoreManager', function () {
this.request.get = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 }, '')
return this.DocstoreManager.getAllRanges(this.project_id, this.callback)
this.DocstoreManager.getAllRanges(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -522,14 +518,11 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 })
return this.DocstoreManager.archiveProject(
this.project_id,
this.callback
)
this.DocstoreManager.archiveProject(this.project_id, this.callback)
})
it('should call the callback', function () {
return this.callback.called.should.equal(true)
this.callback.called.should.equal(true)
})
})
@@ -538,14 +531,11 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 })
return this.DocstoreManager.archiveProject(
this.project_id,
this.callback
)
this.DocstoreManager.archiveProject(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -567,14 +557,11 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 })
return this.DocstoreManager.unarchiveProject(
this.project_id,
this.callback
)
this.DocstoreManager.unarchiveProject(this.project_id, this.callback)
})
it('should call the callback', function () {
return this.callback.called.should.equal(true)
this.callback.called.should.equal(true)
})
})
@@ -583,14 +570,11 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 })
return this.DocstoreManager.unarchiveProject(
this.project_id,
this.callback
)
this.DocstoreManager.unarchiveProject(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -612,14 +596,11 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 204 })
return this.DocstoreManager.destroyProject(
this.project_id,
this.callback
)
this.DocstoreManager.destroyProject(this.project_id, this.callback)
})
it('should call the callback', function () {
return this.callback.called.should.equal(true)
this.callback.called.should.equal(true)
})
})
@@ -628,14 +609,11 @@ describe('DocstoreManager', function () {
this.request.post = sinon
.stub()
.callsArgWith(1, null, { statusCode: 500 })
return this.DocstoreManager.destroyProject(
this.project_id,
this.callback
)
this.DocstoreManager.destroyProject(this.project_id, this.callback)
})
it('should call the callback with an error', function () {
return this.callback
this.callback
.calledWith(
sinon.match
.instanceOf(Error)
@@ -1,21 +1,7 @@
/* eslint-disable
max-len,
no-return-assign,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const sinon = require('sinon')
const { expect } = require('chai')
const modulePath =
'../../../../app/src/Features/Documents/DocumentController.js'
const SandboxedModule = require('sandboxed-module')
const events = require('events')
const MockRequest = require('../helpers/MockRequest')
const MockResponse = require('../helpers/MockResponse')
const Errors = require('../../../../app/src/Features/Errors/Errors')
@@ -41,23 +27,23 @@ describe('DocumentController', function () {
this.pathname = '/a/b/c/file.tex'
this.lastUpdatedAt = new Date().getTime()
this.lastUpdatedBy = 'fake-last-updater-id'
return (this.rev = 5)
this.rev = 5
})
describe('getDocument', function () {
beforeEach(function () {
return (this.req.params = {
this.req.params = {
Project_id: this.project_id,
doc_id: this.doc_id,
})
}
})
describe('when the project exists without project history enabled', function () {
beforeEach(function () {
this.project = { _id: this.project_id }
return (this.ProjectGetter.getProject = sinon
this.ProjectGetter.getProject = sinon
.stub()
.callsArgWith(2, null, this.project))
.callsArgWith(2, null, this.project)
})
describe('when the document exists', function () {
@@ -68,29 +54,18 @@ describe('DocumentController', function () {
.callsArgWith(1, null, this.doc, { fileSystem: this.pathname })
this.ProjectEntityHandler.getDoc = sinon
.stub()
.callsArgWith(
2,
null,
this.doc_lines,
this.rev,
this.version,
this.ranges
)
return this.DocumentController.getDocument(
this.req,
this.res,
this.next
)
.yields(null, this.doc_lines, this.rev, this.version, this.ranges)
this.DocumentController.getDocument(this.req, this.res, this.next)
})
it('should get the project', function () {
return this.ProjectGetter.getProject
this.ProjectGetter.getProject
.calledWith(this.project_id, { rootFolder: true, overleaf: true })
.should.equal(true)
})
it('should get the pathname of the document', function () {
return this.ProjectLocator.findElement
this.ProjectLocator.findElement
.calledWith({
project: this.project,
element_id: this.doc_id,
@@ -100,14 +75,14 @@ describe('DocumentController', function () {
})
it('should get the document content', function () {
return this.ProjectEntityHandler.getDoc
this.ProjectEntityHandler.getDoc
.calledWith(this.project_id, this.doc_id)
.should.equal(true)
})
it('should return the document data to the client as JSON', function () {
this.res.type.should.equal('application/json')
return this.res.body.should.equal(
this.res.body.should.equal(
JSON.stringify({
lines: this.doc_lines,
version: this.version,
@@ -123,15 +98,11 @@ describe('DocumentController', function () {
this.ProjectLocator.findElement = sinon
.stub()
.callsArgWith(1, new Errors.NotFoundError('not found'))
return this.DocumentController.getDocument(
this.req,
this.res,
this.next
)
this.DocumentController.getDocument(this.req, this.res, this.next)
})
it('should call next with the NotFoundError', function () {
return this.next
this.next
.calledWith(sinon.match.instanceOf(Errors.NotFoundError))
.should.equal(true)
})
@@ -161,24 +132,13 @@ describe('DocumentController', function () {
.callsArgWith(1, null, this.doc, { fileSystem: this.pathname })
this.ProjectEntityHandler.getDoc = sinon
.stub()
.callsArgWith(
2,
null,
this.doc_lines,
this.rev,
this.version,
this.ranges
)
return this.DocumentController.getDocument(
this.req,
this.res,
this.next
)
.yields(null, this.doc_lines, this.rev, this.version, this.ranges)
this.DocumentController.getDocument(this.req, this.res, this.next)
})
it('should return the history id and display setting to the client as JSON', function () {
this.res.type.should.equal('application/json')
return this.res.body.should.equal(
this.res.body.should.equal(
JSON.stringify({
lines: this.doc_lines,
version: this.version,
@@ -215,14 +175,7 @@ describe('DocumentController', function () {
.callsArgWith(1, null, this.doc, { fileSystem: this.pathname })
this.ProjectEntityHandler.getDoc = sinon
.stub()
.callsArgWith(
2,
null,
this.doc_lines,
this.rev,
this.version,
this.ranges
)
.yields(null, this.doc_lines, this.rev, this.version, this.ranges)
return this.DocumentController.getDocument(
this.req,
this.res,
@@ -248,25 +201,21 @@ describe('DocumentController', function () {
describe('when the project does not exist', function () {
beforeEach(function () {
this.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
return this.DocumentController.getDocument(
this.req,
this.res,
this.next
)
this.DocumentController.getDocument(this.req, this.res, this.next)
})
it('returns a 404', function () {
return this.res.statusCode.should.equal(404)
this.res.statusCode.should.equal(404)
})
})
})
describe('setDocument', function () {
beforeEach(function () {
return (this.req.params = {
this.req.params = {
Project_id: this.project_id,
doc_id: this.doc_id,
})
}
})
describe('when the document exists', function () {
@@ -279,15 +228,11 @@ describe('DocumentController', function () {
lastUpdatedAt: this.lastUpdatedAt,
lastUpdatedBy: this.lastUpdatedBy,
}
return this.DocumentController.setDocument(
this.req,
this.res,
this.next
)
this.DocumentController.setDocument(this.req, this.res, this.next)
})
it('should update the document in Mongo', function () {
return sinon.assert.calledWith(
sinon.assert.calledWith(
this.ProjectEntityUpdateHandler.updateDocLines,
this.project_id,
this.doc_id,
@@ -300,7 +245,7 @@ describe('DocumentController', function () {
})
it('should return a successful response', function () {
return this.res.success.should.equal(true)
this.res.success.should.equal(true)
})
})
@@ -310,15 +255,11 @@ describe('DocumentController', function () {
.stub()
.yields(new Errors.NotFoundError('document does not exist'))
this.req.body = { lines: this.doc_lines }
return this.DocumentController.setDocument(
this.req,
this.res,
this.next
)
this.DocumentController.setDocument(this.req, this.res, this.next)
})
it('should call next with the NotFoundError', function () {
return this.next
this.next
.calledWith(sinon.match.instanceOf(Errors.NotFoundError))
.should.equal(true)
})