mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
[web] Promisify ProjectRootDocManager (#30327)
* Remove ESLint disable * Unnest ProjectRootDocManager methods * Promisify `setRootDocAutomatically` * Promisify `findRootDocFileFromDirectory` * Promisify `setRootDocFromName` * Promisify `ensureRootDocumentIsSet` * Promisify `ensureRootDocumentIsValid` * Promisify `_sortFileList` * Fixup export * Call `ProjectRootDocManager.promises.setRootDocAutomatically` so mocks work * Update ProjectRootDocManager.test.mjs * Remove expects on callbacks * Add `return` on the first matching entry in `setRootDocAutomatically` See https://github.com/overleaf/internal/pull/30327#discussion_r2624011209 Co-authored-by: Brian Gough <brian.gough@overleaf.com> --------- Co-authored-by: Brian Gough <brian.gough@overleaf.com> GitOrigin-RevId: f83e8239f65776d23b49cb7a569181abc7c9a276
This commit is contained in:
committed by
Copybot
co-authored by
Brian Gough
parent
3d352b35cb
commit
16b83b128b
@@ -1,9 +1,3 @@
|
||||
/* eslint-disable
|
||||
n/handle-callback-err,
|
||||
max-len,
|
||||
no-unused-vars,
|
||||
no-useless-escape,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
@@ -18,321 +12,215 @@ import ProjectGetter from './ProjectGetter.mjs'
|
||||
import DocumentHelper from '../Documents/DocumentHelper.mjs'
|
||||
import Path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import async from 'async'
|
||||
import pLimit from 'p-limit'
|
||||
import globby from 'globby'
|
||||
import _ from 'lodash'
|
||||
import { promisifyAll } from '@overleaf/promise-utils'
|
||||
import { callbackify, callbackifyMultiResult } from '@overleaf/promise-utils'
|
||||
import logger from '@overleaf/logger'
|
||||
import { BackgroundTaskTracker } from '../../infrastructure/GracefulShutdown.mjs'
|
||||
|
||||
const rootDocResets = new BackgroundTaskTracker('root doc resets')
|
||||
const ProjectRootDocManager = {
|
||||
setRootDocAutomaticallyInBackground(projectId) {
|
||||
rootDocResets.add()
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await ProjectRootDocManager.promises.setRootDocAutomatically(projectId)
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
{ err },
|
||||
'failed to set root doc automatically in background'
|
||||
)
|
||||
} finally {
|
||||
rootDocResets.done()
|
||||
}
|
||||
}, 30 * 1000)
|
||||
},
|
||||
|
||||
setRootDocAutomatically(projectId, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
function setRootDocAutomaticallyInBackground(projectId) {
|
||||
rootDocResets.add()
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await ProjectRootDocManager.promises.setRootDocAutomatically(projectId)
|
||||
} catch (err) {
|
||||
logger.warn({ err }, 'failed to set root doc automatically in background')
|
||||
} finally {
|
||||
rootDocResets.done()
|
||||
}
|
||||
return ProjectEntityHandler.getAllDocs(projectId, function (error, docs) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
}, 30 * 1000)
|
||||
}
|
||||
|
||||
const jobs = _.map(
|
||||
docs,
|
||||
(doc, path) =>
|
||||
function (cb) {
|
||||
if (
|
||||
ProjectEntityUpdateHandler.isPathValidForRootDoc(path) &&
|
||||
DocumentHelper.contentHasDocumentclass(doc.lines)
|
||||
) {
|
||||
async.setImmediate(function () {
|
||||
cb(doc._id)
|
||||
})
|
||||
} else {
|
||||
async.setImmediate(function () {
|
||||
cb(null)
|
||||
})
|
||||
}
|
||||
}
|
||||
async function setRootDocAutomatically(projectId) {
|
||||
const docs = await ProjectEntityHandler.promises.getAllDocs(projectId)
|
||||
|
||||
for (const [path, doc] of Object.entries(docs)) {
|
||||
if (
|
||||
ProjectEntityUpdateHandler.isPathValidForRootDoc(path) &&
|
||||
DocumentHelper.contentHasDocumentclass(doc.lines) &&
|
||||
doc._id
|
||||
) {
|
||||
return await ProjectEntityUpdateHandler.promises.setRootDoc(
|
||||
projectId,
|
||||
doc._id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return async.series(jobs, function (rootDocId) {
|
||||
if (rootDocId != null) {
|
||||
return ProjectEntityUpdateHandler.setRootDoc(
|
||||
projectId,
|
||||
rootDocId,
|
||||
callback
|
||||
)
|
||||
} else {
|
||||
return callback()
|
||||
async function findRootDocFileFromDirectory(directoryPath) {
|
||||
const unsortedFiles = await globby(['**/*.{tex,Rtex,Rnw}'], {
|
||||
cwd: directoryPath,
|
||||
followSymlinkedDirectories: false,
|
||||
onlyFiles: true,
|
||||
case: false,
|
||||
})
|
||||
|
||||
// the search order is such that we prefer files closer to the project root, then
|
||||
// we go by file size in ascending order, because people often have a main
|
||||
// file that just includes a bunch of other files; then we go by name, in
|
||||
// order to be deterministic
|
||||
|
||||
const files = await _sortFileList(unsortedFiles, directoryPath)
|
||||
let firstFileInRootFolder
|
||||
let doc = null
|
||||
|
||||
while (files.length > 0 && doc == null) {
|
||||
const file = files.shift()
|
||||
const content = await fs.promises.readFile(
|
||||
Path.join(directoryPath, file),
|
||||
'utf8'
|
||||
)
|
||||
const normalizedContent = (content || '').replace(/\r/g, '')
|
||||
if (DocumentHelper.contentHasDocumentclass(normalizedContent)) {
|
||||
doc = { path: file, content: normalizedContent }
|
||||
}
|
||||
|
||||
if (!firstFileInRootFolder && !file.includes('/')) {
|
||||
firstFileInRootFolder = { path: file, content: normalizedContent }
|
||||
}
|
||||
}
|
||||
|
||||
// if no doc was found, use the first file in the root folder as the main doc
|
||||
if (!doc && firstFileInRootFolder) {
|
||||
doc = firstFileInRootFolder
|
||||
}
|
||||
|
||||
return { path: doc?.path, content: doc?.content }
|
||||
}
|
||||
|
||||
async function setRootDocFromName(projectId, rootDocName) {
|
||||
const docPaths =
|
||||
await ProjectEntityHandler.promises.getAllDocPathsFromProjectById(projectId)
|
||||
|
||||
let docId, path
|
||||
// strip off leading and trailing quotes from rootDocName
|
||||
rootDocName = rootDocName.replace(/^'|'$/g, '')
|
||||
// prepend a slash for the root folder if not present
|
||||
if (rootDocName[0] !== '/') {
|
||||
rootDocName = `/${rootDocName}`
|
||||
}
|
||||
// find the root doc from the filename
|
||||
let rootDocId = null
|
||||
for (docId in docPaths) {
|
||||
// docpaths have a leading / so allow matching "folder/filename" and "/folder/filename"
|
||||
path = docPaths[docId]
|
||||
if (path === rootDocName) {
|
||||
rootDocId = docId
|
||||
}
|
||||
}
|
||||
// try a basename match if there was no match
|
||||
if (!rootDocId) {
|
||||
for (docId in docPaths) {
|
||||
path = docPaths[docId]
|
||||
if (Path.basename(path) === Path.basename(rootDocName)) {
|
||||
rootDocId = docId
|
||||
}
|
||||
}
|
||||
}
|
||||
// set the root doc id if we found a match
|
||||
if (rootDocId != null) {
|
||||
return await ProjectEntityUpdateHandler.promises.setRootDoc(
|
||||
projectId,
|
||||
rootDocId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureRootDocumentIsSet(projectId) {
|
||||
const project = await ProjectGetter.promises.getProject(projectId, {
|
||||
rootDoc_id: 1,
|
||||
})
|
||||
if (!project) {
|
||||
throw new Error('project not found')
|
||||
}
|
||||
if (project.rootDoc_id != null) {
|
||||
return
|
||||
}
|
||||
return await ProjectRootDocManager.promises.setRootDocAutomatically(projectId)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ObjectId | string} projectId
|
||||
*/
|
||||
async function ensureRootDocumentIsValid(projectId) {
|
||||
const project =
|
||||
await ProjectGetter.promises.getProjectWithoutDocLines(projectId)
|
||||
if (!project) {
|
||||
throw new Error('project not found')
|
||||
}
|
||||
if (project.rootDoc_id != null) {
|
||||
const docPath =
|
||||
await ProjectEntityHandler.promises.getDocPathFromProjectByDocId(
|
||||
project,
|
||||
project.rootDoc_id
|
||||
)
|
||||
if (docPath) {
|
||||
return
|
||||
}
|
||||
await ProjectEntityUpdateHandler.promises.unsetRootDoc(projectId)
|
||||
}
|
||||
return await ProjectRootDocManager.promises.setRootDocAutomatically(projectId)
|
||||
}
|
||||
|
||||
async function _sortFileList(listToSort, rootDirectory) {
|
||||
const limit = pLimit(5)
|
||||
const files = await Promise.all(
|
||||
listToSort.map(filePath =>
|
||||
limit(async () => {
|
||||
const fullPath = Path.join(rootDirectory, filePath)
|
||||
const stat = await fs.promises.stat(fullPath)
|
||||
return {
|
||||
size: stat.size,
|
||||
path: filePath,
|
||||
elements: filePath.split(Path.sep).length,
|
||||
name: Path.basename(filePath),
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
findRootDocFileFromDirectory(directoryPath, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
const filePathsPromise = globby(['**/*.{tex,Rtex,Rnw}'], {
|
||||
cwd: directoryPath,
|
||||
followSymlinkedDirectories: false,
|
||||
onlyFiles: true,
|
||||
case: false,
|
||||
})
|
||||
|
||||
// the search order is such that we prefer files closer to the project root, then
|
||||
// we go by file size in ascending order, because people often have a main
|
||||
// file that just includes a bunch of other files; then we go by name, in
|
||||
// order to be deterministic
|
||||
filePathsPromise.then(
|
||||
unsortedFiles =>
|
||||
ProjectRootDocManager._sortFileList(
|
||||
unsortedFiles,
|
||||
directoryPath,
|
||||
function (err, files) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
let firstFileInRootFolder
|
||||
let doc = null
|
||||
|
||||
return async.until(
|
||||
cb => cb(null, doc != null || files.length === 0),
|
||||
function (cb) {
|
||||
const file = files.shift()
|
||||
return fs.readFile(
|
||||
Path.join(directoryPath, file),
|
||||
'utf8',
|
||||
function (error, content) {
|
||||
if (error != null) {
|
||||
return cb(error)
|
||||
}
|
||||
content = (content || '').replace(/\r/g, '')
|
||||
if (DocumentHelper.contentHasDocumentclass(content)) {
|
||||
doc = { path: file, content }
|
||||
}
|
||||
|
||||
if (!firstFileInRootFolder && !file.includes('/')) {
|
||||
firstFileInRootFolder = { path: file, content }
|
||||
}
|
||||
cb(null)
|
||||
}
|
||||
)
|
||||
},
|
||||
err => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
// if no doc was found, use the first file in the root folder as the main doc
|
||||
if (!doc && firstFileInRootFolder) {
|
||||
doc = firstFileInRootFolder
|
||||
}
|
||||
|
||||
callback(null, doc?.path, doc?.content)
|
||||
}
|
||||
)
|
||||
}
|
||||
),
|
||||
err => callback(err)
|
||||
)
|
||||
)
|
||||
return files.sort(_rootDocSort).map(file => file.path)
|
||||
}
|
||||
|
||||
// coffeescript's implicit-return mechanism returns filePathsPromise from this method, which confuses mocha
|
||||
return null
|
||||
},
|
||||
function _rootDocSort(a, b) {
|
||||
// sort first by folder depth
|
||||
if (a.elements !== b.elements) {
|
||||
return a.elements - b.elements
|
||||
}
|
||||
// ensure main.tex is at the start of each folder
|
||||
if (a.name === 'main.tex' && b.name !== 'main.tex') {
|
||||
return -1
|
||||
}
|
||||
if (a.name !== 'main.tex' && b.name === 'main.tex') {
|
||||
return 1
|
||||
}
|
||||
// prefer smaller files
|
||||
if (a.size !== b.size) {
|
||||
return a.size - b.size
|
||||
}
|
||||
// otherwise, use the full path name
|
||||
return a.path.localeCompare(b.path)
|
||||
}
|
||||
|
||||
setRootDocFromName(projectId, rootDocName, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return ProjectEntityHandler.getAllDocPathsFromProjectById(
|
||||
projectId,
|
||||
function (error, docPaths) {
|
||||
let docId, path
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
// strip off leading and trailing quotes from rootDocName
|
||||
rootDocName = rootDocName.replace(/^\'|\'$/g, '')
|
||||
// prepend a slash for the root folder if not present
|
||||
if (rootDocName[0] !== '/') {
|
||||
rootDocName = `/${rootDocName}`
|
||||
}
|
||||
// find the root doc from the filename
|
||||
let rootDocId = null
|
||||
for (docId in docPaths) {
|
||||
// docpaths have a leading / so allow matching "folder/filename" and "/folder/filename"
|
||||
path = docPaths[docId]
|
||||
if (path === rootDocName) {
|
||||
rootDocId = docId
|
||||
}
|
||||
}
|
||||
// try a basename match if there was no match
|
||||
if (!rootDocId) {
|
||||
for (docId in docPaths) {
|
||||
path = docPaths[docId]
|
||||
if (Path.basename(path) === Path.basename(rootDocName)) {
|
||||
rootDocId = docId
|
||||
}
|
||||
}
|
||||
}
|
||||
// set the root doc id if we found a match
|
||||
if (rootDocId != null) {
|
||||
return ProjectEntityUpdateHandler.setRootDoc(
|
||||
projectId,
|
||||
rootDocId,
|
||||
callback
|
||||
)
|
||||
} else {
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
ensureRootDocumentIsSet(projectId, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return ProjectGetter.getProject(
|
||||
projectId,
|
||||
{ rootDoc_id: 1 },
|
||||
function (error, project) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
if (project == null) {
|
||||
return callback(new Error('project not found'))
|
||||
}
|
||||
|
||||
if (project.rootDoc_id != null) {
|
||||
return callback()
|
||||
} else {
|
||||
return ProjectRootDocManager.setRootDocAutomatically(
|
||||
projectId,
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {ObjectId | string} project_id
|
||||
* @param {Function} callback
|
||||
*/
|
||||
ensureRootDocumentIsValid(projectId, callback) {
|
||||
ProjectGetter.getProjectWithoutDocLines(
|
||||
projectId,
|
||||
function (error, project) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
if (project == null) {
|
||||
return callback(new Error('project not found'))
|
||||
}
|
||||
|
||||
if (project.rootDoc_id != null) {
|
||||
ProjectEntityHandler.getDocPathFromProjectByDocId(
|
||||
project,
|
||||
project.rootDoc_id,
|
||||
(err, docPath) => {
|
||||
if (docPath) return callback()
|
||||
ProjectEntityUpdateHandler.unsetRootDoc(projectId, () =>
|
||||
ProjectRootDocManager.setRootDocAutomatically(
|
||||
projectId,
|
||||
callback
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return ProjectRootDocManager.setRootDocAutomatically(
|
||||
projectId,
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
_sortFileList(listToSort, rootDirectory, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return async.mapLimit(
|
||||
listToSort,
|
||||
5,
|
||||
(filePath, cb) =>
|
||||
fs.stat(Path.join(rootDirectory, filePath), function (err, stat) {
|
||||
if (err != null) {
|
||||
return cb(err)
|
||||
}
|
||||
return cb(null, {
|
||||
size: stat.size,
|
||||
path: filePath,
|
||||
elements: filePath.split(Path.sep).length,
|
||||
name: Path.basename(filePath),
|
||||
})
|
||||
}),
|
||||
function (err, files) {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
return callback(
|
||||
null,
|
||||
_.map(
|
||||
files.sort(ProjectRootDocManager._rootDocSort),
|
||||
file => file.path
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
_rootDocSort(a, b) {
|
||||
// sort first by folder depth
|
||||
if (a.elements !== b.elements) {
|
||||
return a.elements - b.elements
|
||||
}
|
||||
// ensure main.tex is at the start of each folder
|
||||
if (a.name === 'main.tex' && b.name !== 'main.tex') {
|
||||
return -1
|
||||
}
|
||||
if (a.name !== 'main.tex' && b.name === 'main.tex') {
|
||||
return 1
|
||||
}
|
||||
// prefer smaller files
|
||||
if (a.size !== b.size) {
|
||||
return a.size - b.size
|
||||
}
|
||||
// otherwise, use the full path name
|
||||
return a.path.localeCompare(b.path)
|
||||
const ProjectRootDocManager = {
|
||||
setRootDocAutomaticallyInBackground,
|
||||
setRootDocAutomatically: callbackify(setRootDocAutomatically),
|
||||
findRootDocFileFromDirectory: callbackifyMultiResult(
|
||||
findRootDocFileFromDirectory,
|
||||
['path', 'content']
|
||||
),
|
||||
setRootDocFromName: callbackify(setRootDocFromName),
|
||||
ensureRootDocumentIsSet: callbackify(ensureRootDocumentIsSet),
|
||||
ensureRootDocumentIsValid: callbackify(ensureRootDocumentIsValid),
|
||||
promises: {
|
||||
setRootDocAutomatically,
|
||||
findRootDocFileFromDirectory,
|
||||
setRootDocFromName,
|
||||
ensureRootDocumentIsSet,
|
||||
ensureRootDocumentIsValid,
|
||||
},
|
||||
}
|
||||
|
||||
ProjectRootDocManager.promises = promisifyAll(ProjectRootDocManager, {
|
||||
without: ['_rootDocSort', 'setRootDocAutomaticallyInBackground'],
|
||||
multiResult: {
|
||||
findRootDocFileFromDirectory: ['path', 'content'],
|
||||
},
|
||||
})
|
||||
|
||||
export default ProjectRootDocManager
|
||||
|
||||
@@ -19,31 +19,41 @@ describe('ProjectRootDocManager', function () {
|
||||
ctx.docPaths[ctx.docId3] = '/nested/chapter1a.tex'
|
||||
ctx.docPaths[ctx.docId4] = '/nested/chapter1b.tex'
|
||||
ctx.sl_req_id = 'sl-req-id-123'
|
||||
ctx.callback = sinon.stub()
|
||||
ctx.globbyFiles = ['a.tex', 'b.tex', 'main.tex']
|
||||
ctx.globby = sinon.stub().resolves(ctx.globbyFiles)
|
||||
|
||||
ctx.fs = {
|
||||
readFile: sinon.stub().callsArgWith(2, new Error('file not found')),
|
||||
stat: sinon.stub().callsArgWith(1, null, { size: 100 }),
|
||||
promises: {
|
||||
readFile: sinon.stub().rejects(new Error('file not found')),
|
||||
stat: sinon.stub().resolves({ size: 100 }),
|
||||
},
|
||||
}
|
||||
ctx.ProjectEntityHandler = {
|
||||
promises: {},
|
||||
}
|
||||
ctx.ProjectEntityUpdateHandler = {
|
||||
promises: {},
|
||||
}
|
||||
ctx.ProjectGetter = {
|
||||
promises: {},
|
||||
}
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectEntityHandler',
|
||||
() => ({
|
||||
default: (ctx.ProjectEntityHandler = {}),
|
||||
default: ctx.ProjectEntityHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock(
|
||||
'../../../../app/src/Features/Project/ProjectEntityUpdateHandler',
|
||||
() => ({
|
||||
default: (ctx.ProjectEntityUpdateHandler = {}),
|
||||
default: ctx.ProjectEntityUpdateHandler,
|
||||
})
|
||||
)
|
||||
|
||||
vi.doMock('../../../../app/src/Features/Project/ProjectGetter', () => ({
|
||||
default: (ctx.ProjectGetter = {}),
|
||||
default: ctx.ProjectGetter,
|
||||
}))
|
||||
|
||||
vi.doMock('../../../../app/src/infrastructure/GracefulShutdown', () => ({
|
||||
@@ -66,7 +76,9 @@ describe('ProjectRootDocManager', function () {
|
||||
|
||||
describe('setRootDocAutomatically', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
ctx.ProjectEntityUpdateHandler.isPathValidForRootDoc = sinon
|
||||
.stub()
|
||||
.returns(true)
|
||||
@@ -100,22 +112,22 @@ describe('ProjectRootDocManager', function () {
|
||||
lines: ['Hello world'],
|
||||
},
|
||||
}
|
||||
ctx.ProjectEntityHandler.getAllDocs = sinon
|
||||
ctx.ProjectEntityHandler.promises.getAllDocs = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.docs)
|
||||
.resolves(ctx.docs)
|
||||
await ctx.ProjectRootDocManager.promises.setRootDocAutomatically(
|
||||
ctx.project_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should check the docs of the project', function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocs
|
||||
ctx.ProjectEntityHandler.promises.getAllDocs
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should set the root doc to the doc containing a documentclass', function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc
|
||||
.calledWith(ctx.project_id, ctx.docId2)
|
||||
.should.equal(true)
|
||||
})
|
||||
@@ -133,16 +145,16 @@ describe('ProjectRootDocManager', function () {
|
||||
lines: ['\\documentclass{article}', '\\input{chapter1}'],
|
||||
},
|
||||
}
|
||||
ctx.ProjectEntityHandler.getAllDocs = sinon
|
||||
ctx.ProjectEntityHandler.promises.getAllDocs = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.docs)
|
||||
.resolves(ctx.docs)
|
||||
await ctx.ProjectRootDocManager.promises.setRootDocAutomatically(
|
||||
ctx.project_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should set the root doc to the doc containing a documentclass', function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc
|
||||
.calledWith(ctx.project_id, ctx.docId2)
|
||||
.should.equal(true)
|
||||
})
|
||||
@@ -160,37 +172,37 @@ describe('ProjectRootDocManager', function () {
|
||||
lines: ['%Example: \\documentclass{article}'],
|
||||
},
|
||||
}
|
||||
ctx.ProjectEntityHandler.getAllDocs = sinon
|
||||
ctx.ProjectEntityHandler.promises.getAllDocs = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.docs)
|
||||
.resolves(ctx.docs)
|
||||
await ctx.ProjectRootDocManager.promises.setRootDocAutomatically(
|
||||
ctx.project_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should not set the root doc to the doc containing a documentclass', function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('findRootDocFileFromDirectory', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fs.readFile
|
||||
.withArgs('/foo/a.tex')
|
||||
.callsArgWith(2, null, 'Hello World!')
|
||||
ctx.fs.readFile
|
||||
ctx.fs.promises.readFile.withArgs('/foo/a.tex').resolves('Hello World!')
|
||||
ctx.fs.promises.readFile
|
||||
.withArgs('/foo/b.tex')
|
||||
.callsArgWith(2, null, "I'm a little teapot, get me out of here.")
|
||||
ctx.fs.readFile
|
||||
.resolves("I'm a little teapot, get me out of here.")
|
||||
ctx.fs.promises.readFile
|
||||
.withArgs('/foo/main.tex')
|
||||
.callsArgWith(2, null, "Help, I'm trapped in a unit testing factory")
|
||||
ctx.fs.readFile
|
||||
.resolves("Help, I'm trapped in a unit testing factory")
|
||||
ctx.fs.promises.readFile
|
||||
.withArgs('/foo/c.tex')
|
||||
.callsArgWith(2, null, 'Tomato, tomahto.')
|
||||
ctx.fs.readFile
|
||||
.resolves('Tomato, tomahto.')
|
||||
ctx.fs.promises.readFile
|
||||
.withArgs('/foo/a/a.tex')
|
||||
.callsArgWith(2, null, 'Potato? Potahto. Potootee!')
|
||||
.resolves('Potato? Potahto. Potootee!')
|
||||
ctx.documentclassContent = '% test\n\\documentclass\n% test'
|
||||
})
|
||||
|
||||
@@ -214,34 +226,34 @@ describe('ProjectRootDocManager', function () {
|
||||
)
|
||||
expect(path).to.equal('a.tex')
|
||||
sinon.assert.callOrder(
|
||||
ctx.fs.readFile.withArgs('/foo/a.tex'),
|
||||
ctx.fs.readFile.withArgs('/foo/b.tex'),
|
||||
ctx.fs.readFile.withArgs('/foo/c.tex'),
|
||||
ctx.fs.readFile.withArgs('/foo/a/a.tex')
|
||||
ctx.fs.promises.readFile.withArgs('/foo/a.tex'),
|
||||
ctx.fs.promises.readFile.withArgs('/foo/b.tex'),
|
||||
ctx.fs.promises.readFile.withArgs('/foo/c.tex'),
|
||||
ctx.fs.promises.readFile.withArgs('/foo/a/a.tex')
|
||||
)
|
||||
})
|
||||
|
||||
it('processes smaller files first', async function (ctx) {
|
||||
ctx.fs.stat.withArgs('/foo/c.tex').callsArgWith(1, null, { size: 1 })
|
||||
ctx.fs.promises.stat.withArgs('/foo/c.tex').resolves({ size: 1 })
|
||||
const { path } =
|
||||
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
|
||||
'/foo'
|
||||
)
|
||||
expect(path).to.equal('c.tex')
|
||||
sinon.assert.callOrder(
|
||||
ctx.fs.readFile.withArgs('/foo/c.tex'),
|
||||
ctx.fs.readFile.withArgs('/foo/a.tex'),
|
||||
ctx.fs.readFile.withArgs('/foo/b.tex'),
|
||||
ctx.fs.readFile.withArgs('/foo/a/a.tex')
|
||||
ctx.fs.promises.readFile.withArgs('/foo/c.tex'),
|
||||
ctx.fs.promises.readFile.withArgs('/foo/a.tex'),
|
||||
ctx.fs.promises.readFile.withArgs('/foo/b.tex'),
|
||||
ctx.fs.promises.readFile.withArgs('/foo/a/a.tex')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when main.tex contains a documentclass', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fs.readFile
|
||||
ctx.fs.promises.readFile
|
||||
.withArgs('/foo/main.tex')
|
||||
.callsArgWith(2, null, ctx.documentclassContent)
|
||||
.resolves(ctx.documentclassContent)
|
||||
})
|
||||
|
||||
it('returns main.tex', async function (ctx) {
|
||||
@@ -254,20 +266,22 @@ describe('ProjectRootDocManager', function () {
|
||||
})
|
||||
|
||||
it('processes main.text first and stops processing when it finds the content', async function (ctx) {
|
||||
await ctx.ProjectRootDocManager.findRootDocFileFromDirectory('/foo')
|
||||
expect(ctx.fs.readFile).to.be.calledWith('/foo/main.tex')
|
||||
expect(ctx.fs.readFile).not.to.be.calledWith('/foo/a.tex')
|
||||
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
|
||||
'/foo'
|
||||
)
|
||||
expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/main.tex')
|
||||
expect(ctx.fs.promises.readFile).not.to.be.calledWith('/foo/a.tex')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when main.tex does not contain a line starting with \\documentclass', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fs.readFile.withArgs('/foo/a.tex').callsArgWith(2, null, 'foo')
|
||||
ctx.fs.readFile.withArgs('/foo/main.tex').callsArgWith(2, null, 'foo')
|
||||
ctx.fs.readFile.withArgs('/foo/z.tex').callsArgWith(2, null, 'foo')
|
||||
ctx.fs.readFile
|
||||
ctx.fs.promises.readFile.withArgs('/foo/a.tex').resolves('foo')
|
||||
ctx.fs.promises.readFile.withArgs('/foo/main.tex').resolves('foo')
|
||||
ctx.fs.promises.readFile.withArgs('/foo/z.tex').resolves('foo')
|
||||
ctx.fs.promises.readFile
|
||||
.withArgs('/foo/nested/chapter1a.tex')
|
||||
.callsArgWith(2, null, 'foo')
|
||||
.resolves('foo')
|
||||
})
|
||||
|
||||
it('returns the first .tex file from the root folder', async function (ctx) {
|
||||
@@ -308,9 +322,9 @@ describe('ProjectRootDocManager', function () {
|
||||
|
||||
describe('when a.tex contains a documentclass', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fs.readFile
|
||||
ctx.fs.promises.readFile
|
||||
.withArgs('/foo/a.tex')
|
||||
.callsArgWith(2, null, ctx.documentclassContent)
|
||||
.resolves(ctx.documentclassContent)
|
||||
})
|
||||
|
||||
it('returns a.tex', async function (ctx) {
|
||||
@@ -326,9 +340,9 @@ describe('ProjectRootDocManager', function () {
|
||||
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
|
||||
'/foo'
|
||||
)
|
||||
expect(ctx.fs.readFile).to.be.calledWith('/foo/main.tex')
|
||||
expect(ctx.fs.readFile).to.be.calledWith('/foo/a.tex')
|
||||
expect(ctx.fs.readFile).not.to.be.calledWith('/foo/b.tex')
|
||||
expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/main.tex')
|
||||
expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/a.tex')
|
||||
expect(ctx.fs.promises.readFile).not.to.be.calledWith('/foo/b.tex')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -343,17 +357,17 @@ describe('ProjectRootDocManager', function () {
|
||||
await ctx.ProjectRootDocManager.promises.findRootDocFileFromDirectory(
|
||||
'/foo'
|
||||
)
|
||||
expect(ctx.fs.readFile).to.be.calledWith('/foo/main.tex')
|
||||
expect(ctx.fs.readFile).to.be.calledWith('/foo/a.tex')
|
||||
expect(ctx.fs.readFile).to.be.calledWith('/foo/b.tex')
|
||||
expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/main.tex')
|
||||
expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/a.tex')
|
||||
expect(ctx.fs.promises.readFile).to.be.calledWith('/foo/b.tex')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is an error reading a file', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.fs.readFile
|
||||
ctx.fs.promises.readFile
|
||||
.withArgs('/foo/a.tex')
|
||||
.callsArgWith(2, new Error('something went wrong'))
|
||||
.rejects(new Error('something went wrong'))
|
||||
})
|
||||
|
||||
it('returns an error', async function (ctx) {
|
||||
@@ -375,10 +389,12 @@ describe('ProjectRootDocManager', function () {
|
||||
describe('setRootDocFromName', function () {
|
||||
describe('when there is a suitable root doc', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
|
||||
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
|
||||
.resolves(ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
|
||||
ctx.project_id,
|
||||
'/main.tex'
|
||||
@@ -386,13 +402,13 @@ describe('ProjectRootDocManager', function () {
|
||||
})
|
||||
|
||||
it('should check the docs of the project', function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById
|
||||
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should set the root doc to main.tex', function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc
|
||||
.calledWith(ctx.project_id, ctx.docId2.toString())
|
||||
.should.equal(true)
|
||||
})
|
||||
@@ -400,10 +416,12 @@ describe('ProjectRootDocManager', function () {
|
||||
|
||||
describe('when there is a suitable root doc but the leading slash is missing', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
|
||||
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
|
||||
.resolves(ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
|
||||
ctx.project_id,
|
||||
'main.tex'
|
||||
@@ -411,13 +429,13 @@ describe('ProjectRootDocManager', function () {
|
||||
})
|
||||
|
||||
it('should check the docs of the project', function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById
|
||||
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should set the root doc to main.tex', function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc
|
||||
.calledWith(ctx.project_id, ctx.docId2.toString())
|
||||
.should.equal(true)
|
||||
})
|
||||
@@ -425,10 +443,12 @@ describe('ProjectRootDocManager', function () {
|
||||
|
||||
describe('when there is a suitable root doc with a basename match', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
|
||||
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
|
||||
.resolves(ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
|
||||
ctx.project_id,
|
||||
'chapter1a.tex'
|
||||
@@ -436,13 +456,13 @@ describe('ProjectRootDocManager', function () {
|
||||
})
|
||||
|
||||
it('should check the docs of the project', function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById
|
||||
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should set the root doc using the basename', function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc
|
||||
.calledWith(ctx.project_id, ctx.docId3.toString())
|
||||
.should.equal(true)
|
||||
})
|
||||
@@ -450,10 +470,12 @@ describe('ProjectRootDocManager', function () {
|
||||
|
||||
describe('when there is a suitable root doc but the filename is in quotes', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
|
||||
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
|
||||
.resolves(ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
|
||||
ctx.project_id,
|
||||
"'main.tex'"
|
||||
@@ -461,13 +483,13 @@ describe('ProjectRootDocManager', function () {
|
||||
})
|
||||
|
||||
it('should check the docs of the project', function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById
|
||||
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should set the root doc to main.tex', function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc
|
||||
.calledWith(ctx.project_id, ctx.docId2.toString())
|
||||
.should.equal(true)
|
||||
})
|
||||
@@ -475,10 +497,12 @@ describe('ProjectRootDocManager', function () {
|
||||
|
||||
describe('when there is no suitable root doc', function () {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectEntityHandler.getAllDocPathsFromProjectById = sinon
|
||||
ctx.ProjectEntityHandler.promises.getAllDocPathsFromProjectById = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().callsArgWith(2)
|
||||
.resolves(ctx.docPaths)
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
|
||||
.stub()
|
||||
.resolves()
|
||||
await ctx.ProjectRootDocManager.promises.setRootDocFromName(
|
||||
ctx.project_id,
|
||||
'other.tex'
|
||||
@@ -486,7 +510,9 @@ describe('ProjectRootDocManager', function () {
|
||||
})
|
||||
|
||||
it('should not set the root doc', function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc.called.should.equal(false)
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -494,82 +520,65 @@ describe('ProjectRootDocManager', function () {
|
||||
describe('ensureRootDocumentIsSet', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.project = {}
|
||||
ctx.ProjectGetter.getProject = sinon
|
||||
ctx.ProjectGetter.promises.getProject = sinon.stub().resolves(ctx.project)
|
||||
ctx.ProjectRootDocManager.promises.setRootDocAutomatically = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, ctx.project)
|
||||
ctx.ProjectRootDocManager.setRootDocAutomatically = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null)
|
||||
.resolves()
|
||||
})
|
||||
|
||||
describe('when the root doc is set', function () {
|
||||
beforeEach(function (ctx) {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.project.rootDoc_id = ctx.docId2
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsSet(
|
||||
ctx.project_id,
|
||||
ctx.callback
|
||||
|
||||
await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsSet(
|
||||
ctx.project_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should find the project fetching only the rootDoc_id field', function (ctx) {
|
||||
ctx.ProjectGetter.getProject
|
||||
ctx.ProjectGetter.promises.getProject
|
||||
.calledWith(ctx.project_id, { rootDoc_id: 1 })
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should not try to update the project rootDoc_id', function (ctx) {
|
||||
ctx.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
|
||||
ctx.ProjectRootDocManager.promises.setRootDocAutomatically.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should call the callback', function (ctx) {
|
||||
ctx.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the root doc is not set', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsSet(
|
||||
ctx.project_id,
|
||||
ctx.callback
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsSet(
|
||||
ctx.project_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should find the project with only the rootDoc_id field', function (ctx) {
|
||||
ctx.ProjectGetter.getProject
|
||||
ctx.ProjectGetter.promises.getProject
|
||||
.calledWith(ctx.project_id, { rootDoc_id: 1 })
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should update the project rootDoc_id', function (ctx) {
|
||||
ctx.ProjectRootDocManager.setRootDocAutomatically
|
||||
ctx.ProjectRootDocManager.promises.setRootDocAutomatically
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback', function (ctx) {
|
||||
ctx.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project does not exist', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, null)
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsSet(
|
||||
ctx.project_id,
|
||||
ctx.callback
|
||||
)
|
||||
ctx.ProjectGetter.promises.getProject = sinon.stub().resolves(null)
|
||||
})
|
||||
|
||||
it('should call the callback with an error', function (ctx) {
|
||||
ctx.callback
|
||||
.calledWith(
|
||||
sinon.match
|
||||
.instanceOf(Error)
|
||||
.and(sinon.match.has('message', 'project not found'))
|
||||
it('should reject', async function (ctx) {
|
||||
await expect(
|
||||
ctx.ProjectRootDocManager.promises.ensureRootDocumentIsSet(
|
||||
ctx.project_id
|
||||
)
|
||||
.should.equal(true)
|
||||
).to.be.rejectedWith('project not found')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -577,129 +586,110 @@ describe('ProjectRootDocManager', function () {
|
||||
describe('ensureRootDocumentIsValid', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.project = {}
|
||||
ctx.ProjectGetter.getProject = sinon
|
||||
ctx.ProjectGetter.promises.getProject = sinon.stub().resolves(ctx.project)
|
||||
ctx.ProjectGetter.promises.getProjectWithoutDocLines = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, ctx.project)
|
||||
ctx.ProjectGetter.getProjectWithoutDocLines = sinon
|
||||
.resolves(ctx.project)
|
||||
ctx.ProjectEntityUpdateHandler.promises.setRootDoc = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, ctx.project)
|
||||
ctx.ProjectEntityUpdateHandler.setRootDoc = sinon.stub().yields()
|
||||
ctx.ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
|
||||
ctx.ProjectRootDocManager.setRootDocAutomatically = sinon
|
||||
.resolves()
|
||||
ctx.ProjectEntityUpdateHandler.promises.unsetRootDoc = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null)
|
||||
.resolves()
|
||||
ctx.ProjectRootDocManager.promises.setRootDocAutomatically = sinon
|
||||
.stub()
|
||||
.resolves(null)
|
||||
})
|
||||
|
||||
describe('when the root doc is set', function () {
|
||||
describe('when the root doc is valid', function () {
|
||||
beforeEach(function (ctx) {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.project.rootDoc_id = ctx.docId2
|
||||
ctx.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
|
||||
ctx.ProjectEntityHandler.promises.getDocPathFromProjectByDocId = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, ctx.docPaths[ctx.docId2])
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsValid(
|
||||
ctx.project_id,
|
||||
ctx.callback
|
||||
.resolves(ctx.docPaths[ctx.docId2])
|
||||
await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid(
|
||||
ctx.project_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should find the project without doc lines', function (ctx) {
|
||||
ctx.ProjectGetter.getProjectWithoutDocLines
|
||||
ctx.ProjectGetter.promises.getProjectWithoutDocLines
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should not try to update the project rootDoc_id', function (ctx) {
|
||||
ctx.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
|
||||
ctx.ProjectRootDocManager.promises.setRootDocAutomatically.called.should.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should call the callback', function (ctx) {
|
||||
ctx.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the root doc is not valid', function () {
|
||||
beforeEach(function (ctx) {
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.project.rootDoc_id = new ObjectId()
|
||||
ctx.ProjectEntityHandler.getDocPathFromProjectByDocId = sinon
|
||||
ctx.ProjectEntityHandler.promises.getDocPathFromProjectByDocId = sinon
|
||||
.stub()
|
||||
.callsArgWith(2, null, null)
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsValid(
|
||||
ctx.project_id,
|
||||
ctx.callback
|
||||
.resolves(null)
|
||||
await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid(
|
||||
ctx.project_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should find the project without doc lines', function (ctx) {
|
||||
ctx.ProjectGetter.getProjectWithoutDocLines
|
||||
ctx.ProjectGetter.promises.getProjectWithoutDocLines
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should unset the root doc', function (ctx) {
|
||||
ctx.ProjectEntityUpdateHandler.unsetRootDoc
|
||||
ctx.ProjectEntityUpdateHandler.promises.unsetRootDoc
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should try to find a new rootDoc', function (ctx) {
|
||||
ctx.ProjectRootDocManager.setRootDocAutomatically.called.should.equal(
|
||||
ctx.ProjectRootDocManager.promises.setRootDocAutomatically.called.should.equal(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should call the callback', function (ctx) {
|
||||
ctx.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the root doc is not set', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsValid(
|
||||
ctx.project_id,
|
||||
ctx.callback
|
||||
beforeEach(async function (ctx) {
|
||||
await ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid(
|
||||
ctx.project_id
|
||||
)
|
||||
})
|
||||
|
||||
it('should find the project without doc lines', function (ctx) {
|
||||
ctx.ProjectGetter.getProjectWithoutDocLines
|
||||
it('should find the project without doc lines', async function (ctx) {
|
||||
ctx.ProjectGetter.promises.getProjectWithoutDocLines
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should update the project rootDoc_id', function (ctx) {
|
||||
ctx.ProjectRootDocManager.setRootDocAutomatically
|
||||
ctx.ProjectRootDocManager.promises.setRootDocAutomatically
|
||||
.calledWith(ctx.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should call the callback', function (ctx) {
|
||||
ctx.callback.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project does not exist', function () {
|
||||
beforeEach(function (ctx) {
|
||||
ctx.ProjectGetter.getProjectWithoutDocLines = sinon
|
||||
beforeEach(async function (ctx) {
|
||||
ctx.ProjectGetter.promises.getProjectWithoutDocLines = sinon
|
||||
.stub()
|
||||
.callsArgWith(1, null, null)
|
||||
ctx.ProjectRootDocManager.ensureRootDocumentIsValid(
|
||||
ctx.project_id,
|
||||
ctx.callback
|
||||
)
|
||||
.resolves(null)
|
||||
})
|
||||
|
||||
it('should call the callback with an error', function (ctx) {
|
||||
ctx.callback
|
||||
.calledWith(
|
||||
sinon.match
|
||||
.instanceOf(Error)
|
||||
.and(sinon.match.has('message', 'project not found'))
|
||||
it('should reject', async function (ctx) {
|
||||
await expect(
|
||||
ctx.ProjectRootDocManager.promises.ensureRootDocumentIsValid(
|
||||
ctx.project_id
|
||||
)
|
||||
.should.equal(true)
|
||||
).to.be.rejectedWith('project not found')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user