| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356 |
- const { expect } = require('chai')
- const sinon = require('sinon')
- const Errors = require('../../../../app/src/Features/Errors/Errors')
- const SandboxedModule = require('sandboxed-module')
- const { ObjectId } = require('mongodb')
- const MODULE_PATH =
- '../../../../app/src/Features/Project/ProjectEntityUpdateHandler'
- describe('ProjectEntityUpdateHandler', function () {
- const projectId = '4eecb1c1bffa66588e0000a1'
- const projectHistoryId = '123456'
- const docId = '4eecb1c1bffa66588e0000a2'
- const fileId = '4eecaffcbffa66588e000009'
- const folderId = '4eecaffcbffa66588e000008'
- const newFileId = '4eecaffcbffa66588e000099'
- const userId = 1234
- beforeEach(function () {
- this.project = {
- _id: projectId,
- name: 'project name',
- overleaf: {
- history: {
- id: projectHistoryId,
- },
- },
- }
- this.fileUrl = 'filestore.example.com/file'
- this.user = { _id: new ObjectId() }
- this.DocModel = class Doc {
- constructor(options) {
- this.name = options.name
- this.lines = options.lines
- this._id = docId
- this.rev = 0
- }
- }
- this.FileModel = class File {
- constructor(options) {
- this.name = options.name
- // use a new id for replacement files
- if (this.name === 'dummy-upload-filename') {
- this._id = newFileId
- } else {
- this._id = fileId
- }
- this.rev = 0
- if (options.linkedFileData != null) {
- this.linkedFileData = options.linkedFileData
- }
- if (options.hash != null) {
- this.hash = options.hash
- }
- }
- }
- this.docName = 'doc-name'
- this.docLines = ['1234', 'abc']
- this.doc = { _id: new ObjectId(), name: this.docName }
- this.fileName = 'something.jpg'
- this.fileSystemPath = 'somehintg'
- this.file = { _id: new ObjectId(), name: this.fileName, rev: 2 }
- this.linkedFileData = { provider: 'url' }
- this.source = 'editor'
- this.callback = sinon.stub()
- this.DocstoreManager = {
- getDoc: sinon.stub(),
- isDocDeleted: sinon.stub(),
- updateDoc: sinon.stub(),
- deleteDoc: sinon.stub(),
- }
- this.DocumentUpdaterHandler = {
- flushDocToMongo: sinon.stub().yields(),
- updateProjectStructure: sinon.stub().yields(),
- setDocument: sinon.stub(),
- resyncProjectHistory: sinon.stub(),
- deleteDoc: sinon.stub().yields(),
- }
- this.fs = {
- unlink: sinon.stub().yields(),
- }
- this.LockManager = {
- runWithLock: sinon.spy((namespace, id, runner, callback) =>
- runner(callback)
- ),
- }
- this.ProjectModel = {
- updateOne: sinon.stub(),
- }
- this.ProjectGetter = {
- getProject: sinon.stub(),
- getProjectWithoutDocLines: sinon.stub(),
- }
- this.ProjectLocator = {
- findElement: sinon.stub(),
- findElementByPath: sinon.stub(),
- }
- this.ProjectUpdater = {
- markAsUpdated: sinon.stub(),
- }
- this.ProjectEntityHandler = {
- getDoc: sinon.stub(),
- getDocPathByProjectIdAndDocId: sinon.stub(),
- getAllEntitiesFromProject: sinon.stub(),
- }
- this.ProjectEntityMongoUpdateHandler = {
- addDoc: sinon.stub(),
- addFile: sinon.stub(),
- addFolder: sinon.stub(),
- _confirmFolder: sinon.stub(),
- _putElement: sinon.stub(),
- _insertDeletedFileReference: sinon.stub(),
- replaceFileWithNew: sinon.stub(),
- mkdirp: sinon.stub(),
- moveEntity: sinon.stub(),
- renameEntity: sinon.stub(),
- deleteEntity: sinon.stub(),
- replaceDocWithFile: sinon.stub(),
- replaceFileWithDoc: sinon.stub(),
- }
- this.TpdsUpdateSender = {
- addFile: sinon.stub().yields(),
- addDoc: sinon.stub(),
- deleteEntity: sinon.stub().yields(),
- moveEntity: sinon.stub(),
- promises: {
- moveEntity: sinon.stub().resolves(),
- },
- }
- this.FileStoreHandler = {
- copyFile: sinon.stub(),
- uploadFileFromDisk: sinon.stub(),
- deleteFile: sinon.stub(),
- }
- this.FileWriter = {
- writeLinesToDisk: sinon.stub(),
- }
- this.EditorRealTimeController = {
- emitToRoom: sinon.stub(),
- }
- this.ProjectEntityUpdateHandler = SandboxedModule.require(MODULE_PATH, {
- requires: {
- '@overleaf/settings': { validRootDocExtensions: ['tex'] },
- fs: this.fs,
- '../../models/Doc': { Doc: this.DocModel },
- '../Docstore/DocstoreManager': this.DocstoreManager,
- '../../Features/DocumentUpdater/DocumentUpdaterHandler': this
- .DocumentUpdaterHandler,
- '../../models/File': { File: this.FileModel },
- '../FileStore/FileStoreHandler': this.FileStoreHandler,
- '../../infrastructure/LockManager': this.LockManager,
- '../../models/Project': { Project: this.ProjectModel },
- './ProjectGetter': this.ProjectGetter,
- './ProjectLocator': this.ProjectLocator,
- './ProjectUpdateHandler': this.ProjectUpdater,
- './ProjectEntityHandler': this.ProjectEntityHandler,
- './ProjectEntityMongoUpdateHandler': this
- .ProjectEntityMongoUpdateHandler,
- '../ThirdPartyDataStore/TpdsUpdateSender': this.TpdsUpdateSender,
- '../Editor/EditorRealTimeController': this.EditorRealTimeController,
- '../../infrastructure/FileWriter': this.FileWriter,
- },
- })
- })
- describe('updateDocLines', function () {
- beforeEach(function () {
- this.path = '/somewhere/something.tex'
- this.doc = {
- _id: docId,
- }
- this.version = 42
- this.ranges = { mock: 'ranges' }
- this.lastUpdatedAt = new Date().getTime()
- this.lastUpdatedBy = 'fake-last-updater-id'
- this.DocstoreManager.isDocDeleted.yields(null, false)
- this.ProjectGetter.getProject.yields(null, this.project)
- this.ProjectLocator.findElement.yields(null, this.doc, {
- fileSystem: this.path,
- })
- this.TpdsUpdateSender.addDoc.yields()
- })
- describe('when the doc has been modified', function () {
- beforeEach(function () {
- this.DocstoreManager.updateDoc.yields(null, true, (this.rev = 5))
- this.ProjectEntityUpdateHandler.updateDocLines(
- projectId,
- docId,
- this.docLines,
- this.version,
- this.ranges,
- this.lastUpdatedAt,
- this.lastUpdatedBy,
- this.callback
- )
- })
- it('should get the project with very few fields', function () {
- this.ProjectGetter.getProject
- .calledWith(projectId, {
- name: true,
- rootFolder: true,
- })
- .should.equal(true)
- })
- it('should find the doc', function () {
- this.ProjectLocator.findElement
- .calledWith({
- project: this.project,
- type: 'docs',
- element_id: docId,
- })
- .should.equal(true)
- })
- it('should update the doc in the docstore', function () {
- this.DocstoreManager.updateDoc
- .calledWith(
- projectId,
- docId,
- this.docLines,
- this.version,
- this.ranges
- )
- .should.equal(true)
- })
- it('should mark the project as updated', function () {
- sinon.assert.calledWith(
- this.ProjectUpdater.markAsUpdated,
- projectId,
- this.lastUpdatedAt,
- this.lastUpdatedBy
- )
- })
- it('should send the doc the to the TPDS', function () {
- this.TpdsUpdateSender.addDoc
- .calledWith({
- project_id: projectId,
- project_name: this.project.name,
- doc_id: docId,
- rev: this.rev,
- path: this.path,
- })
- .should.equal(true)
- })
- it('should call the callback', function () {
- this.callback.called.should.equal(true)
- })
- })
- describe('when the doc has not been modified', function () {
- beforeEach(function () {
- this.DocstoreManager.updateDoc.yields(null, false, (this.rev = 5))
- this.ProjectEntityUpdateHandler.updateDocLines(
- projectId,
- docId,
- this.docLines,
- this.version,
- this.ranges,
- this.lastUpdatedAt,
- this.lastUpdatedBy,
- this.callback
- )
- })
- it('should not mark the project as updated', function () {
- this.ProjectUpdater.markAsUpdated.called.should.equal(false)
- })
- it('should not send the doc the to the TPDS', function () {
- this.TpdsUpdateSender.addDoc.called.should.equal(false)
- })
- it('should call the callback', function () {
- this.callback.called.should.equal(true)
- })
- })
- describe('when the doc has been deleted', function () {
- beforeEach(function () {
- this.ProjectGetter.getProject.yields(null, this.project)
- this.ProjectLocator.findElement.yields(new Errors.NotFoundError())
- this.DocstoreManager.isDocDeleted.yields(null, true)
- this.DocstoreManager.updateDoc.yields()
- this.ProjectEntityUpdateHandler.updateDocLines(
- projectId,
- docId,
- this.docLines,
- this.version,
- this.ranges,
- this.lastUpdatedAt,
- this.lastUpdatedBy,
- this.callback
- )
- })
- it('should update the doc in the docstore', function () {
- this.DocstoreManager.updateDoc
- .calledWith(
- projectId,
- docId,
- this.docLines,
- this.version,
- this.ranges
- )
- .should.equal(true)
- })
- it('should not mark the project as updated', function () {
- this.ProjectUpdater.markAsUpdated.called.should.equal(false)
- })
- it('should not send the doc the to the TPDS', function () {
- this.TpdsUpdateSender.addDoc.called.should.equal(false)
- })
- it('should call the callback', function () {
- this.callback.called.should.equal(true)
- })
- })
- describe('when projects and docs collection are de-synced', function () {
- beforeEach(function () {
- this.ProjectGetter.getProject.yields(null, this.project)
- // The doc is not in the file-tree, but also not marked as deleted.
- // This should not happen, but web should handle it.
- this.ProjectLocator.findElement.yields(new Errors.NotFoundError())
- this.DocstoreManager.isDocDeleted.yields(null, false)
- this.DocstoreManager.updateDoc.yields()
- this.ProjectEntityUpdateHandler.updateDocLines(
- projectId,
- docId,
- this.docLines,
- this.version,
- this.ranges,
- this.lastUpdatedAt,
- this.lastUpdatedBy,
- this.callback
- )
- })
- it('should update the doc in the docstore', function () {
- this.DocstoreManager.updateDoc
- .calledWith(
- projectId,
- docId,
- this.docLines,
- this.version,
- this.ranges
- )
- .should.equal(true)
- })
- it('should not mark the project as updated', function () {
- this.ProjectUpdater.markAsUpdated.called.should.equal(false)
- })
- it('should not send the doc the to the TPDS', function () {
- this.TpdsUpdateSender.addDoc.called.should.equal(false)
- })
- it('should call the callback', function () {
- this.callback.called.should.equal(true)
- })
- })
- describe('when the doc is not related to the project', function () {
- beforeEach(function () {
- this.ProjectGetter.getProject.yields(null, this.project)
- this.ProjectLocator.findElement.yields(new Errors.NotFoundError())
- this.DocstoreManager.isDocDeleted.yields(new Errors.NotFoundError())
- this.ProjectEntityUpdateHandler.updateDocLines(
- projectId,
- docId,
- this.docLines,
- this.version,
- this.ranges,
- this.lastUpdatedAt,
- this.lastUpdatedBy,
- this.callback
- )
- })
- it('should return a not found error', function () {
- this.callback
- .calledWith(sinon.match.instanceOf(Errors.NotFoundError))
- .should.equal(true)
- })
- it('should not update the doc', function () {
- this.DocstoreManager.updateDoc.called.should.equal(false)
- })
- it('should not send the doc the to the TPDS', function () {
- this.TpdsUpdateSender.addDoc.called.should.equal(false)
- })
- })
- describe('when the project is not found', function () {
- beforeEach(function () {
- this.ProjectGetter.getProject.yields(new Errors.NotFoundError())
- this.ProjectEntityUpdateHandler.updateDocLines(
- projectId,
- docId,
- this.docLines,
- this.version,
- this.ranges,
- this.lastUpdatedAt,
- this.lastUpdatedBy,
- this.callback
- )
- })
- it('should return a not found error', function () {
- this.callback
- .calledWith(sinon.match.instanceOf(Errors.NotFoundError))
- .should.equal(true)
- })
- it('should not update the doc', function () {
- this.DocstoreManager.updateDoc.called.should.equal(false)
- })
- it('should not send the doc the to the TPDS', function () {
- this.TpdsUpdateSender.addDoc.called.should.equal(false)
- })
- })
- })
- describe('setRootDoc', function () {
- beforeEach(function () {
- this.rootDocId = 'root-doc-id-123123'
- })
- it('should call Project.updateOne when the doc exists and has a valid extension', function () {
- this.ProjectEntityHandler.getDocPathByProjectIdAndDocId.yields(
- null,
- `/main.tex`
- )
- this.ProjectEntityUpdateHandler.setRootDoc(
- projectId,
- this.rootDocId,
- () => {}
- )
- this.ProjectModel.updateOne
- .calledWith({ _id: projectId }, { rootDoc_id: this.rootDocId })
- .should.equal(true)
- })
- it("should not call Project.updateOne when the doc doesn't exist", function () {
- this.ProjectEntityHandler.getDocPathByProjectIdAndDocId.yields(
- Errors.NotFoundError
- )
- this.ProjectEntityUpdateHandler.setRootDoc(
- projectId,
- this.rootDocId,
- () => {}
- )
- this.ProjectModel.updateOne
- .calledWith({ _id: projectId }, { rootDoc_id: this.rootDocId })
- .should.equal(false)
- })
- it('should call the callback with an UnsupportedFileTypeError when the doc has an unaccepted file extension', function () {
- this.ProjectEntityHandler.getDocPathByProjectIdAndDocId.yields(
- null,
- `/foo/bar.baz`
- )
- this.ProjectEntityUpdateHandler.setRootDoc(
- projectId,
- this.rootDocId,
- this.callback
- )
- expect(this.callback.firstCall.args[0]).to.be.an.instanceof(
- Errors.UnsupportedFileTypeError
- )
- })
- })
- describe('unsetRootDoc', function () {
- it('should call Project.updateOne', function () {
- this.ProjectEntityUpdateHandler.unsetRootDoc(projectId)
- this.ProjectModel.updateOne
- .calledWith({ _id: projectId }, { $unset: { rootDoc_id: true } })
- .should.equal(true)
- })
- })
- describe('addDoc', function () {
- describe('adding a doc', function () {
- beforeEach(function () {
- this.path = '/path/to/doc'
- this.newDoc = new this.DocModel({
- name: this.docName,
- lines: undefined,
- _id: docId,
- rev: 0,
- })
- this.DocstoreManager.updateDoc.yields(null, false, (this.rev = 5))
- this.TpdsUpdateSender.addDoc.yields()
- this.ProjectEntityMongoUpdateHandler.addDoc.yields(
- null,
- { path: { fileSystem: this.path } },
- this.project
- )
- this.ProjectEntityUpdateHandler.addDoc(
- projectId,
- docId,
- this.docName,
- this.docLines,
- userId,
- this.callback
- )
- })
- it('creates the doc without history', function () {
- this.DocstoreManager.updateDoc
- .calledWith(projectId, docId, this.docLines, 0, {})
- .should.equal(true)
- })
- it('sends the change in project structure to the doc updater', function () {
- const newDocs = [
- {
- doc: this.newDoc,
- path: this.path,
- docLines: this.docLines.join('\n'),
- },
- ]
- this.DocumentUpdaterHandler.updateProjectStructure
- .calledWith(projectId, projectHistoryId, userId, {
- newDocs,
- newProject: this.project,
- })
- .should.equal(true)
- })
- })
- describe('adding a doc with an invalid name', function () {
- beforeEach(function () {
- this.path = '/path/to/doc'
- this.newDoc = { _id: docId }
- this.ProjectEntityUpdateHandler.addDoc(
- projectId,
- folderId,
- `*${this.docName}`,
- this.docLines,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- })
- describe('addFile', function () {
- describe('adding a file', function () {
- beforeEach(function () {
- this.path = '/path/to/file'
- this.newFile = {
- _id: fileId,
- rev: 0,
- name: this.fileName,
- linkedFileData: this.linkedFileData,
- }
- this.FileStoreHandler.uploadFileFromDisk.yields(
- null,
- this.fileUrl,
- this.newFile
- )
- this.TpdsUpdateSender.addFile.yields()
- this.ProjectEntityMongoUpdateHandler.addFile.yields(
- null,
- { path: { fileSystem: this.path } },
- this.project
- )
- this.ProjectEntityUpdateHandler.addFile(
- projectId,
- folderId,
- this.fileName,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('updates the file in the filestore', function () {
- this.FileStoreHandler.uploadFileFromDisk
- .calledWith(
- projectId,
- { name: this.fileName, linkedFileData: this.linkedFileData },
- this.fileSystemPath
- )
- .should.equal(true)
- })
- it('updates the file in mongo', function () {
- const fileMatcher = sinon.match(file => {
- return file.name === this.fileName
- })
- this.ProjectEntityMongoUpdateHandler.addFile
- .calledWithMatch(projectId, folderId, fileMatcher)
- .should.equal(true)
- })
- it('notifies the tpds', function () {
- this.TpdsUpdateSender.addFile
- .calledWith({
- project_id: projectId,
- project_name: this.project.name,
- file_id: fileId,
- rev: 0,
- path: this.path,
- })
- .should.equal(true)
- })
- it('should mark the project as updated', function () {
- const args = this.ProjectUpdater.markAsUpdated.args[0]
- args[0].should.equal(projectId)
- args[1].should.exist
- args[2].should.equal(userId)
- })
- it('sends the change in project structure to the doc updater', function () {
- const newFiles = [
- {
- file: this.newFile,
- path: this.path,
- url: this.fileUrl,
- },
- ]
- this.DocumentUpdaterHandler.updateProjectStructure
- .calledWith(projectId, projectHistoryId, userId, {
- newFiles,
- newProject: this.project,
- })
- .should.equal(true)
- })
- })
- describe('adding a file with an invalid name', function () {
- beforeEach(function () {
- this.path = '/path/to/file'
- this.newFile = {
- _id: fileId,
- rev: 0,
- name: this.fileName,
- linkedFileData: this.linkedFileData,
- }
- this.TpdsUpdateSender.addFile.yields()
- this.ProjectEntityMongoUpdateHandler.addFile.yields(
- null,
- { path: { fileSystem: this.path } },
- this.project
- )
- this.ProjectEntityUpdateHandler.addFile(
- projectId,
- folderId,
- `*${this.fileName}`,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- })
- describe('replaceFile', function () {
- beforeEach(function () {
- // replacement file now creates a new file object
- this.newFileUrl = 'new-file-url'
- this.newFile = {
- _id: newFileId,
- name: 'dummy-upload-filename',
- rev: 0,
- linkedFileData: this.linkedFileData,
- }
- this.oldFile = { _id: fileId, rev: 3 }
- this.path = '/path/to/file'
- this.newProject = 'new project'
- this.FileStoreHandler.uploadFileFromDisk.yields(
- null,
- this.newFileUrl,
- this.newFile
- )
- this.ProjectEntityMongoUpdateHandler._insertDeletedFileReference.yields()
- this.ProjectEntityMongoUpdateHandler.replaceFileWithNew.yields(
- null,
- this.oldFile,
- this.project,
- { fileSystem: this.path },
- this.newProject
- )
- this.ProjectEntityUpdateHandler.replaceFile(
- projectId,
- fileId,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('uploads a new version of the file', function () {
- this.FileStoreHandler.uploadFileFromDisk
- .calledWith(
- projectId,
- {
- name: 'dummy-upload-filename',
- linkedFileData: this.linkedFileData,
- },
- this.fileSystemPath
- )
- .should.equal(true)
- })
- it('replaces the file in mongo', function () {
- this.ProjectEntityMongoUpdateHandler.replaceFileWithNew
- .calledWith(projectId, fileId, this.newFile)
- .should.equal(true)
- })
- it('notifies the tpds', function () {
- this.TpdsUpdateSender.addFile
- .calledWith({
- project_id: projectId,
- project_name: this.project.name,
- file_id: newFileId,
- rev: this.oldFile.rev + 1,
- path: this.path,
- })
- .should.equal(true)
- })
- it('should mark the project as updated', function () {
- const args = this.ProjectUpdater.markAsUpdated.args[0]
- args[0].should.equal(projectId)
- args[1].should.exist
- args[2].should.equal(userId)
- })
- it('updates the project structure in the doc updater', function () {
- const oldFiles = [
- {
- file: this.oldFile,
- path: this.path,
- },
- ]
- const newFiles = [
- {
- file: this.newFile,
- path: this.path,
- url: this.newFileUrl,
- },
- ]
- this.DocumentUpdaterHandler.updateProjectStructure
- .calledWith(projectId, projectHistoryId, userId, {
- oldFiles,
- newFiles,
- newProject: this.newProject,
- })
- .should.equal(true)
- })
- })
- describe('upsertDoc', function () {
- describe('upserting into an invalid folder', function () {
- beforeEach(function () {
- this.ProjectLocator.findElement.yields()
- this.ProjectEntityUpdateHandler.upsertDoc(
- projectId,
- folderId,
- this.docName,
- this.docLines,
- this.source,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Error)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- describe('updating an existing doc', function () {
- beforeEach(function () {
- this.existingDoc = { _id: docId, name: this.docName }
- this.existingFile = { _id: fileId, name: this.fileName }
- this.folder = {
- _id: folderId,
- docs: [this.existingDoc],
- fileRefs: [this.existingFile],
- }
- this.ProjectLocator.findElement.yields(null, this.folder)
- this.DocumentUpdaterHandler.setDocument.yields()
- this.ProjectEntityUpdateHandler.upsertDoc(
- projectId,
- folderId,
- this.docName,
- this.docLines,
- this.source,
- userId,
- this.callback
- )
- })
- it('tries to find the folder', function () {
- this.ProjectLocator.findElement
- .calledWith({
- project_id: projectId,
- element_id: folderId,
- type: 'folder',
- })
- .should.equal(true)
- })
- it('updates the doc contents', function () {
- this.DocumentUpdaterHandler.setDocument
- .calledWith(
- projectId,
- this.existingDoc._id,
- userId,
- this.docLines,
- this.source
- )
- .should.equal(true)
- })
- it('flushes the doc contents', function () {
- this.DocumentUpdaterHandler.flushDocToMongo
- .calledWith(projectId, this.existingDoc._id)
- .should.equal(true)
- })
- it('returns the doc', function () {
- this.callback.calledWith(null, this.existingDoc, false)
- })
- })
- describe('creating a new doc', function () {
- beforeEach(function () {
- this.folder = { _id: folderId, docs: [], fileRefs: [] }
- this.newDoc = { _id: docId }
- this.ProjectLocator.findElement.yields(null, this.folder)
- this.ProjectEntityUpdateHandler.addDocWithRanges = {
- withoutLock: sinon.stub().yields(null, this.newDoc),
- }
- this.ProjectEntityUpdateHandler.upsertDoc(
- projectId,
- folderId,
- this.docName,
- this.docLines,
- this.source,
- userId,
- this.callback
- )
- })
- it('tries to find the folder', function () {
- this.ProjectLocator.findElement
- .calledWith({
- project_id: projectId,
- element_id: folderId,
- type: 'folder',
- })
- .should.equal(true)
- })
- it('adds the doc', function () {
- this.ProjectEntityUpdateHandler.addDocWithRanges.withoutLock
- .calledWith(
- projectId,
- folderId,
- this.docName,
- this.docLines,
- {},
- userId
- )
- .should.equal(true)
- })
- it('returns the doc', function () {
- this.callback.calledWith(null, this.newDoc, true)
- })
- })
- describe('upserting a new doc with an invalid name', function () {
- beforeEach(function () {
- this.folder = { _id: folderId, docs: [], fileRefs: [] }
- this.newDoc = { _id: docId }
- this.ProjectLocator.findElement.yields(null, this.folder)
- this.ProjectEntityUpdateHandler.addDocWithRanges = {
- withoutLock: sinon.stub().yields(null, this.newDoc),
- }
- this.ProjectEntityUpdateHandler.upsertDoc(
- projectId,
- folderId,
- `*${this.docName}`,
- this.docLines,
- this.source,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- describe('upserting a doc on top of a file', function () {
- beforeEach(function () {
- this.newProject = {
- name: 'new project',
- overleaf: { history: { id: projectHistoryId } },
- }
- this.existingFile = { _id: fileId, name: 'foo.tex', rev: 12 }
- this.folder = { _id: folderId, docs: [], fileRefs: [this.existingFile] }
- this.newDoc = { _id: docId }
- this.docLines = ['line one', 'line two']
- this.folderPath = '/path/to/folder'
- this.filePath = '/path/to/folder/foo.tex'
- this.ProjectLocator.findElement
- .withArgs({
- project_id: projectId,
- element_id: this.folder._id,
- type: 'folder',
- })
- .yields(null, this.folder, {
- fileSystem: this.folderPath,
- })
- this.DocstoreManager.updateDoc.yields()
- this.ProjectEntityMongoUpdateHandler.replaceFileWithDoc.yields(
- null,
- this.newProject
- )
- this.TpdsUpdateSender.addDoc.yields()
- this.ProjectEntityUpdateHandler.upsertDoc(
- projectId,
- folderId,
- 'foo.tex',
- this.docLines,
- this.source,
- userId,
- this.callback
- )
- })
- it('notifies docstore of the new doc', function () {
- expect(this.DocstoreManager.updateDoc).to.have.been.calledWith(
- projectId,
- this.newDoc._id,
- this.docLines
- )
- })
- it('adds the new doc and removes the file in one go', function () {
- expect(
- this.ProjectEntityMongoUpdateHandler.replaceFileWithDoc
- ).to.have.been.calledWithMatch(
- projectId,
- this.existingFile._id,
- this.newDoc
- )
- })
- it('sends the doc to TPDS', function () {
- expect(this.TpdsUpdateSender.addDoc).to.have.been.calledWith({
- project_id: projectId,
- doc_id: this.newDoc._id,
- path: this.filePath,
- project_name: this.newProject.name,
- rev: this.existingFile.rev + 1,
- })
- })
- it('sends the updates to the doc updater', function () {
- const oldFiles = [
- {
- file: this.existingFile,
- path: this.filePath,
- },
- ]
- const newDocs = [
- {
- doc: sinon.match(this.newDoc),
- path: this.filePath,
- docLines: this.docLines.join('\n'),
- },
- ]
- expect(
- this.DocumentUpdaterHandler.updateProjectStructure
- ).to.have.been.calledWith(projectId, projectHistoryId, userId, {
- oldFiles,
- newDocs,
- newProject: this.newProject,
- })
- })
- it('should notify everyone of the file deletion', function () {
- expect(
- this.EditorRealTimeController.emitToRoom
- ).to.have.been.calledWith(
- projectId,
- 'removeEntity',
- this.existingFile._id,
- 'convertFileToDoc'
- )
- })
- })
- })
- describe('upsertFile', function () {
- beforeEach(function () {
- this.FileStoreHandler.uploadFileFromDisk.yields(
- null,
- this.fileUrl,
- this.newFile
- )
- })
- describe('upserting into an invalid folder', function () {
- beforeEach(function () {
- this.ProjectLocator.findElement.yields()
- this.ProjectEntityUpdateHandler.upsertFile(
- projectId,
- folderId,
- this.fileName,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Error)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- describe('updating an existing file', function () {
- beforeEach(function () {
- this.existingFile = { _id: fileId, name: this.fileName }
- this.folder = { _id: folderId, fileRefs: [this.existingFile], docs: [] }
- this.ProjectLocator.findElement.yields(null, this.folder)
- this.ProjectEntityUpdateHandler.replaceFile = {
- mainTask: sinon.stub().yields(null, this.newFile),
- }
- this.ProjectEntityUpdateHandler.upsertFile(
- projectId,
- folderId,
- this.fileName,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('replaces the file', function () {
- expect(
- this.ProjectEntityUpdateHandler.replaceFile.mainTask
- ).to.be.calledWith(
- projectId,
- fileId,
- this.fileSystemPath,
- this.linkedFileData,
- userId
- )
- })
- it('returns the file', function () {
- this.callback.calledWith(null, this.existingFile, false)
- })
- })
- describe('creating a new file', function () {
- beforeEach(function () {
- this.folder = { _id: folderId, fileRefs: [], docs: [] }
- this.newFile = { _id: fileId }
- this.ProjectLocator.findElement.yields(null, this.folder)
- this.ProjectEntityUpdateHandler.addFile = {
- mainTask: sinon.stub().yields(null, this.newFile),
- }
- this.ProjectEntityUpdateHandler.upsertFile(
- projectId,
- folderId,
- this.fileName,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('tries to find the folder', function () {
- this.ProjectLocator.findElement
- .calledWith({
- project_id: projectId,
- element_id: folderId,
- type: 'folder',
- })
- .should.equal(true)
- })
- it('adds the file', function () {
- this.ProjectEntityUpdateHandler.addFile.mainTask
- .calledWith(
- projectId,
- folderId,
- this.fileName,
- this.fileSystemPath,
- this.linkedFileData,
- userId
- )
- .should.equal(true)
- })
- it('returns the file', function () {
- this.callback.calledWith(null, this.newFile, true)
- })
- })
- describe('upserting a new file with an invalid name', function () {
- beforeEach(function () {
- this.folder = { _id: folderId, fileRefs: [] }
- this.newFile = { _id: fileId }
- this.ProjectLocator.findElement.yields(null, this.folder)
- this.ProjectEntityUpdateHandler.addFile = {
- mainTask: sinon.stub().yields(null, this.newFile),
- }
- this.ProjectEntityUpdateHandler.upsertFile(
- projectId,
- folderId,
- `*${this.fileName}`,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- describe('upserting file on top of a doc', function () {
- beforeEach(function (done) {
- this.path = '/path/to/doc'
- this.existingDoc = { _id: new ObjectId(), name: this.fileName }
- this.folder = {
- _id: folderId,
- fileRefs: [],
- docs: [this.existingDoc],
- }
- this.ProjectLocator.findElement
- .withArgs({
- project_id: this.project._id.toString(),
- element_id: folderId,
- type: 'folder',
- })
- .yields(null, this.folder)
- this.ProjectLocator.findElement
- .withArgs({
- project_id: this.project._id.toString(),
- element_id: this.existingDoc._id,
- type: 'doc',
- })
- .yields(null, this.existingDoc, { fileSystem: this.path })
- this.newFileUrl = 'new-file-url'
- this.newFile = {
- _id: newFileId,
- name: 'dummy-upload-filename',
- rev: 0,
- linkedFileData: this.linkedFileData,
- }
- this.newProject = {
- name: 'new project',
- overleaf: { history: { id: projectHistoryId } },
- }
- this.FileStoreHandler.uploadFileFromDisk.yields(
- null,
- this.newFileUrl,
- this.newFile
- )
- this.ProjectEntityMongoUpdateHandler.replaceDocWithFile.yields(
- null,
- this.newProject
- )
- this.ProjectEntityUpdateHandler.upsertFile(
- projectId,
- folderId,
- this.fileName,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- done
- )
- })
- it('replaces the existing doc with a file', function () {
- expect(
- this.ProjectEntityMongoUpdateHandler.replaceDocWithFile
- ).to.have.been.calledWith(projectId, this.existingDoc._id, this.newFile)
- })
- it('updates the doc structure', function () {
- const oldDocs = [
- {
- doc: this.existingDoc,
- path: this.path,
- },
- ]
- const newFiles = [
- {
- file: this.newFile,
- path: this.path,
- url: this.newFileUrl,
- },
- ]
- const updates = {
- oldDocs,
- newFiles,
- newProject: this.newProject,
- }
- expect(
- this.DocumentUpdaterHandler.updateProjectStructure
- ).to.have.been.calledWith(projectId, projectHistoryId, userId, updates)
- })
- it('tells everyone in the room the doc is removed', function () {
- expect(
- this.EditorRealTimeController.emitToRoom
- ).to.have.been.calledWith(
- projectId,
- 'removeEntity',
- this.existingDoc._id,
- 'convertDocToFile'
- )
- })
- })
- })
- describe('upsertDocWithPath', function () {
- describe('upserting a doc', function () {
- beforeEach(function () {
- this.path = '/folder/doc.tex'
- this.newFolders = ['mock-a', 'mock-b']
- this.folder = { _id: folderId }
- this.doc = { _id: docId }
- this.isNewDoc = true
- this.ProjectEntityUpdateHandler.mkdirp = {
- withoutLock: sinon.stub().yields(null, this.newFolders, this.folder),
- }
- this.ProjectEntityUpdateHandler.upsertDoc = {
- withoutLock: sinon.stub().yields(null, this.doc, this.isNewDoc),
- }
- this.ProjectEntityUpdateHandler.upsertDocWithPath(
- projectId,
- this.path,
- this.docLines,
- this.source,
- userId,
- this.callback
- )
- })
- it('creates any necessary folders', function () {
- this.ProjectEntityUpdateHandler.mkdirp.withoutLock
- .calledWith(projectId, '/folder')
- .should.equal(true)
- })
- it('upserts the doc', function () {
- this.ProjectEntityUpdateHandler.upsertDoc.withoutLock
- .calledWith(
- projectId,
- this.folder._id,
- 'doc.tex',
- this.docLines,
- this.source,
- userId
- )
- .should.equal(true)
- })
- it('calls the callback', function () {
- this.callback
- .calledWith(
- null,
- this.doc,
- this.isNewDoc,
- this.newFolders,
- this.folder
- )
- .should.equal(true)
- })
- })
- describe('upserting a doc with an invalid path', function () {
- beforeEach(function () {
- this.path = '/*folder/doc.tex'
- this.newFolders = ['mock-a', 'mock-b']
- this.folder = { _id: folderId }
- this.doc = { _id: docId }
- this.isNewDoc = true
- this.ProjectEntityUpdateHandler.mkdirp = {
- withoutLock: sinon.stub().yields(null, this.newFolders, this.folder),
- }
- this.ProjectEntityUpdateHandler.upsertDoc = {
- withoutLock: sinon.stub().yields(null, this.doc, this.isNewDoc),
- }
- this.ProjectEntityUpdateHandler.upsertDocWithPath(
- projectId,
- this.path,
- this.docLines,
- this.source,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- describe('upserting a doc with an invalid name', function () {
- beforeEach(function () {
- this.path = '/folder/*doc.tex'
- this.newFolders = ['mock-a', 'mock-b']
- this.folder = { _id: folderId }
- this.doc = { _id: docId }
- this.isNewDoc = true
- this.ProjectEntityUpdateHandler.mkdirp = {
- withoutLock: sinon.stub().yields(null, this.newFolders, this.folder),
- }
- this.ProjectEntityUpdateHandler.upsertDoc = {
- withoutLock: sinon.stub().yields(null, this.doc, this.isNewDoc),
- }
- this.ProjectEntityUpdateHandler.upsertDocWithPath(
- projectId,
- this.path,
- this.docLines,
- this.source,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- })
- describe('upsertFileWithPath', function () {
- describe('upserting a file', function () {
- beforeEach(function () {
- this.path = '/folder/file.png'
- this.newFolders = ['mock-a', 'mock-b']
- this.folder = { _id: folderId }
- this.file = { _id: fileId }
- this.isNewFile = true
- this.FileStoreHandler.uploadFileFromDisk.yields(
- null,
- this.fileUrl,
- this.newFile
- )
- this.ProjectEntityUpdateHandler.mkdirp = {
- withoutLock: sinon.stub().yields(null, this.newFolders, this.folder),
- }
- this.ProjectEntityUpdateHandler.upsertFile = {
- mainTask: sinon.stub().yields(null, this.file, this.isNewFile),
- }
- this.ProjectEntityUpdateHandler.upsertFileWithPath(
- projectId,
- this.path,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('creates any necessary folders', function () {
- this.ProjectEntityUpdateHandler.mkdirp.withoutLock
- .calledWith(projectId, '/folder')
- .should.equal(true)
- })
- it('upserts the file', function () {
- this.ProjectEntityUpdateHandler.upsertFile.mainTask
- .calledWith(
- projectId,
- this.folder._id,
- 'file.png',
- this.fileSystemPath,
- this.linkedFileData,
- userId
- )
- .should.equal(true)
- })
- it('calls the callback', function () {
- this.callback
- .calledWith(
- null,
- this.file,
- this.isNewFile,
- undefined,
- this.newFolders,
- this.folder
- )
- .should.equal(true)
- })
- })
- describe('upserting a file with an invalid path', function () {
- beforeEach(function () {
- this.path = '/*folder/file.png'
- this.newFolders = ['mock-a', 'mock-b']
- this.folder = { _id: folderId }
- this.file = { _id: fileId }
- this.isNewFile = true
- this.ProjectEntityUpdateHandler.mkdirp = {
- withoutLock: sinon.stub().yields(null, this.newFolders, this.folder),
- }
- this.ProjectEntityUpdateHandler.upsertFile = {
- mainTask: sinon.stub().yields(null, this.file, this.isNewFile),
- }
- this.ProjectEntityUpdateHandler.upsertFileWithPath(
- projectId,
- this.path,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- describe('upserting a file with an invalid name', function () {
- beforeEach(function () {
- this.path = '/folder/*file.png'
- this.newFolders = ['mock-a', 'mock-b']
- this.folder = { _id: folderId }
- this.file = { _id: fileId }
- this.isNewFile = true
- this.ProjectEntityUpdateHandler.mkdirp = {
- withoutLock: sinon.stub().yields(null, this.newFolders, this.folder),
- }
- this.ProjectEntityUpdateHandler.upsertFile = {
- mainTask: sinon.stub().yields(null, this.file, this.isNewFile),
- }
- this.ProjectEntityUpdateHandler.upsertFileWithPath(
- projectId,
- this.path,
- this.fileSystemPath,
- this.linkedFileData,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- })
- describe('deleteEntity', function () {
- beforeEach(function () {
- this.path = '/path/to/doc.tex'
- this.doc = { _id: docId }
- this.projectBeforeDeletion = { _id: projectId, name: 'project' }
- this.newProject = 'new-project'
- this.ProjectEntityMongoUpdateHandler.deleteEntity.yields(
- null,
- this.doc,
- { fileSystem: this.path },
- this.projectBeforeDeletion,
- this.newProject
- )
- this.ProjectEntityUpdateHandler._cleanUpEntity = sinon.stub().yields()
- this.ProjectEntityUpdateHandler.deleteEntity(
- projectId,
- docId,
- 'doc',
- userId,
- this.callback
- )
- })
- it('deletes the entity in mongo', function () {
- this.ProjectEntityMongoUpdateHandler.deleteEntity
- .calledWith(projectId, docId, 'doc')
- .should.equal(true)
- })
- it('cleans up the doc in the docstore', function () {
- this.ProjectEntityUpdateHandler._cleanUpEntity
- .calledWith(
- this.projectBeforeDeletion,
- this.newProject,
- this.doc,
- 'doc',
- this.path,
- userId
- )
- .should.equal(true)
- })
- it('it notifies the tpds', function () {
- this.TpdsUpdateSender.deleteEntity
- .calledWith({
- project_id: projectId,
- path: this.path,
- project_name: this.projectBeforeDeletion.name,
- })
- .should.equal(true)
- })
- it('retuns the entity_id', function () {
- this.callback.calledWith(null, docId).should.equal(true)
- })
- })
- describe('deleteEntityWithPath', function () {
- describe('when the entity exists', function () {
- beforeEach(function () {
- this.doc = { _id: docId }
- this.ProjectLocator.findElementByPath.yields(null, this.doc, 'doc')
- this.ProjectEntityUpdateHandler.deleteEntity = {
- withoutLock: sinon.stub().yields(),
- }
- this.path = '/path/to/doc.tex'
- this.ProjectEntityUpdateHandler.deleteEntityWithPath(
- projectId,
- this.path,
- userId,
- this.callback
- )
- })
- it('finds the entity', function () {
- this.ProjectLocator.findElementByPath
- .calledWith({ project_id: projectId, path: this.path })
- .should.equal(true)
- })
- it('deletes the entity', function () {
- this.ProjectEntityUpdateHandler.deleteEntity.withoutLock
- .calledWith(projectId, this.doc._id, 'doc', userId, this.callback)
- .should.equal(true)
- })
- })
- describe('when the entity does not exist', function () {
- beforeEach(function () {
- this.ProjectLocator.findElementByPath.yields()
- this.path = '/doc.tex'
- this.ProjectEntityUpdateHandler.deleteEntityWithPath(
- projectId,
- this.path,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- this.callback
- .calledWith(sinon.match.instanceOf(Errors.NotFoundError))
- .should.equal(true)
- })
- })
- })
- describe('mkdirp', function () {
- beforeEach(function () {
- this.docPath = '/folder/doc.tex'
- this.ProjectEntityMongoUpdateHandler.mkdirp.yields()
- this.ProjectEntityUpdateHandler.mkdirp(
- projectId,
- this.docPath,
- this.callback
- )
- })
- it('calls ProjectEntityMongoUpdateHandler', function () {
- this.ProjectEntityMongoUpdateHandler.mkdirp
- .calledWith(projectId, this.docPath)
- .should.equal(true)
- })
- })
- describe('mkdirpWithExactCase', function () {
- beforeEach(function () {
- this.docPath = '/folder/doc.tex'
- this.ProjectEntityMongoUpdateHandler.mkdirp.yields()
- this.ProjectEntityUpdateHandler.mkdirpWithExactCase(
- projectId,
- this.docPath,
- this.callback
- )
- })
- it('calls ProjectEntityMongoUpdateHandler', function () {
- this.ProjectEntityMongoUpdateHandler.mkdirp
- .calledWith(projectId, this.docPath, { exactCaseMatch: true })
- .should.equal(true)
- })
- })
- describe('addFolder', function () {
- describe('adding a folder', function () {
- beforeEach(function () {
- this.parentFolderId = '123asdf'
- this.folderName = 'new-folder'
- this.ProjectEntityMongoUpdateHandler.addFolder.yields()
- this.ProjectEntityUpdateHandler.addFolder(
- projectId,
- this.parentFolderId,
- this.folderName,
- this.callback
- )
- })
- it('calls ProjectEntityMongoUpdateHandler', function () {
- this.ProjectEntityMongoUpdateHandler.addFolder
- .calledWith(projectId, this.parentFolderId, this.folderName)
- .should.equal(true)
- })
- })
- describe('adding a folder with an invalid name', function () {
- beforeEach(function () {
- this.parentFolderId = '123asdf'
- this.folderName = '*new-folder'
- this.ProjectEntityMongoUpdateHandler.addFolder.yields()
- this.ProjectEntityUpdateHandler.addFolder(
- projectId,
- this.parentFolderId,
- this.folderName,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- })
- describe('moveEntity', function () {
- beforeEach(function () {
- this.project_name = 'project name'
- this.startPath = '/a.tex'
- this.endPath = '/folder/b.tex'
- this.rev = 2
- this.changes = { newDocs: ['old-doc'], newFiles: ['old-file'] }
- this.ProjectEntityMongoUpdateHandler.moveEntity.yields(
- null,
- this.project,
- this.startPath,
- this.endPath,
- this.rev,
- this.changes
- )
- this.ProjectEntityUpdateHandler.moveEntity(
- projectId,
- docId,
- folderId,
- 'doc',
- userId,
- this.callback
- )
- })
- it('moves the entity in mongo', function () {
- this.ProjectEntityMongoUpdateHandler.moveEntity
- .calledWith(projectId, docId, folderId, 'doc')
- .should.equal(true)
- })
- it('notifies tpds', function () {
- this.TpdsUpdateSender.promises.moveEntity
- .calledWith({
- project_id: projectId,
- project_name: this.project_name,
- startPath: this.startPath,
- endPath: this.endPath,
- rev: this.rev,
- })
- .should.equal(true)
- })
- it('sends the changes in project structure to the doc updater', function () {
- this.DocumentUpdaterHandler.updateProjectStructure
- .calledWith(
- projectId,
- projectHistoryId,
- userId,
- this.changes,
- this.callback
- )
- .should.equal(true)
- })
- })
- describe('renameEntity', function () {
- describe('renaming an entity', function () {
- beforeEach(function () {
- this.project_name = 'project name'
- this.startPath = '/folder/a.tex'
- this.endPath = '/folder/b.tex'
- this.rev = 2
- this.changes = { newDocs: ['old-doc'], newFiles: ['old-file'] }
- this.newDocName = 'b.tex'
- this.ProjectEntityMongoUpdateHandler.renameEntity.yields(
- null,
- this.project,
- this.startPath,
- this.endPath,
- this.rev,
- this.changes
- )
- this.ProjectEntityUpdateHandler.renameEntity(
- projectId,
- docId,
- 'doc',
- this.newDocName,
- userId,
- this.callback
- )
- })
- it('moves the entity in mongo', function () {
- this.ProjectEntityMongoUpdateHandler.renameEntity
- .calledWith(projectId, docId, 'doc', this.newDocName)
- .should.equal(true)
- })
- it('notifies tpds', function () {
- this.TpdsUpdateSender.promises.moveEntity
- .calledWith({
- project_id: projectId,
- project_name: this.project_name,
- startPath: this.startPath,
- endPath: this.endPath,
- rev: this.rev,
- })
- .should.equal(true)
- })
- it('sends the changes in project structure to the doc updater', function () {
- this.DocumentUpdaterHandler.updateProjectStructure
- .calledWith(
- projectId,
- projectHistoryId,
- userId,
- this.changes,
- this.callback
- )
- .should.equal(true)
- })
- })
- describe('renaming an entity to an invalid name', function () {
- beforeEach(function () {
- this.project_name = 'project name'
- this.startPath = '/folder/a.tex'
- this.endPath = '/folder/b.tex'
- this.rev = 2
- this.changes = { newDocs: ['old-doc'], newFiles: ['old-file'] }
- this.newDocName = '*b.tex'
- this.ProjectEntityMongoUpdateHandler.renameEntity.yields(
- null,
- this.project,
- this.startPath,
- this.endPath,
- this.rev,
- this.changes
- )
- this.ProjectEntityUpdateHandler.renameEntity(
- projectId,
- docId,
- 'doc',
- this.newDocName,
- userId,
- this.callback
- )
- })
- it('returns an error', function () {
- const errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
- this.callback.calledWithMatch(errorMatcher).should.equal(true)
- })
- })
- })
- describe('resyncProjectHistory', function () {
- describe('a deleted project', function () {
- beforeEach(function () {
- this.ProjectGetter.getProject.yields()
- this.ProjectEntityUpdateHandler.resyncProjectHistory(
- projectId,
- this.callback
- )
- })
- it('should return an error', function () {
- expect(this.callback).to.have.been.calledWith(
- sinon.match
- .instanceOf(Errors.ProjectHistoryDisabledError)
- .and(
- sinon.match.has(
- 'message',
- `project history not enabled for ${projectId}`
- )
- )
- )
- })
- })
- describe('a project without project-history enabled', function () {
- beforeEach(function () {
- this.project.overleaf = {}
- this.ProjectGetter.getProject.yields(null, this.project)
- this.ProjectEntityUpdateHandler.resyncProjectHistory(
- projectId,
- this.callback
- )
- })
- it('should return an error', function () {
- expect(this.callback).to.have.been.calledWith(
- sinon.match
- .instanceOf(Errors.ProjectHistoryDisabledError)
- .and(
- sinon.match.has(
- 'message',
- `project history not enabled for ${projectId}`
- )
- )
- )
- })
- })
- describe('a project with project-history enabled', function () {
- beforeEach(function () {
- this.ProjectGetter.getProject.yields(null, this.project)
- const docs = [
- {
- doc: {
- _id: docId,
- },
- path: 'main.tex',
- },
- ]
- const files = [
- {
- file: {
- _id: fileId,
- hash: '123456',
- },
- path: 'universe.png',
- },
- ]
- this.ProjectEntityHandler.getAllEntitiesFromProject.yields(
- null,
- docs,
- files
- )
- this.FileStoreHandler._buildUrl = (projectId, fileId) =>
- `www.filestore.test/${projectId}/${fileId}`
- this.DocumentUpdaterHandler.resyncProjectHistory.yields()
- this.ProjectEntityUpdateHandler.resyncProjectHistory(
- projectId,
- this.callback
- )
- })
- it('gets the project', function () {
- this.ProjectGetter.getProject.calledWith(projectId).should.equal(true)
- })
- it('gets the entities for the project', function () {
- this.ProjectEntityHandler.getAllEntitiesFromProject
- .calledWith(this.project)
- .should.equal(true)
- })
- it('tells the doc updater to sync the project', function () {
- const docs = [
- {
- doc: docId,
- path: 'main.tex',
- },
- ]
- const files = [
- {
- file: fileId,
- path: 'universe.png',
- url: `www.filestore.test/${projectId}/${fileId}`,
- _hash: '123456',
- },
- ]
- this.DocumentUpdaterHandler.resyncProjectHistory
- .calledWith(projectId, projectHistoryId, docs, files)
- .should.equal(true)
- })
- it('calls the callback', function () {
- this.callback.called.should.equal(true)
- })
- })
- })
- describe('_cleanUpEntity', function () {
- beforeEach(function () {
- this.entityId = '4eecaffcbffa66588e000009'
- this.FileStoreHandler.deleteFile.yields()
- this.ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
- this.ProjectEntityMongoUpdateHandler._insertDeletedFileReference.yields()
- })
- describe('a file', function () {
- beforeEach(function (done) {
- this.path = '/file/system/path.png'
- this.entity = { _id: this.entityId }
- this.newProject = 'new-project'
- this.ProjectEntityUpdateHandler._cleanUpEntity(
- this.project,
- this.newProject,
- this.entity,
- 'file',
- this.path,
- userId,
- done
- )
- })
- it('should insert the file into the deletedFiles collection', function () {
- this.ProjectEntityMongoUpdateHandler._insertDeletedFileReference
- .calledWith(this.project._id, this.entity)
- .should.equal(true)
- })
- it('should not delete the file from FileStoreHandler', function () {
- this.FileStoreHandler.deleteFile
- .calledWith(projectId, this.entityId)
- .should.equal(false)
- })
- it('should not attempt to delete from the document updater', function () {
- this.DocumentUpdaterHandler.deleteDoc.called.should.equal(false)
- })
- it('should should send the update to the doc updater', function () {
- const oldFiles = [{ file: this.entity, path: this.path }]
- this.DocumentUpdaterHandler.updateProjectStructure
- .calledWith(projectId, projectHistoryId, userId, {
- oldFiles,
- newProject: this.newProject,
- })
- .should.equal(true)
- })
- })
- describe('a doc', function () {
- beforeEach(function (done) {
- this.path = '/file/system/path.tex'
- this.ProjectEntityUpdateHandler._cleanUpDoc = sinon.stub().yields()
- this.entity = { _id: this.entityId }
- this.newProject = 'new-project'
- this.ProjectEntityUpdateHandler._cleanUpEntity(
- this.project,
- this.newProject,
- this.entity,
- 'doc',
- this.path,
- userId,
- done
- )
- })
- it('should clean up the doc', function () {
- this.ProjectEntityUpdateHandler._cleanUpDoc
- .calledWith(this.project, this.entity, this.path, userId)
- .should.equal(true)
- })
- it('should should send the update to the doc updater', function () {
- const oldDocs = [{ doc: this.entity, path: this.path }]
- this.DocumentUpdaterHandler.updateProjectStructure
- .calledWith(projectId, projectHistoryId, userId, {
- oldDocs,
- newProject: this.newProject,
- })
- .should.equal(true)
- })
- })
- describe('a folder', function () {
- beforeEach(function (done) {
- this.folder = {
- folders: [
- {
- name: 'subfolder',
- fileRefs: [
- (this.file1 = { _id: 'file-id-1', name: 'file-name-1' }),
- ],
- docs: [(this.doc1 = { _id: 'doc-id-1', name: 'doc-name-1' })],
- folders: [],
- },
- ],
- fileRefs: [(this.file2 = { _id: 'file-id-2', name: 'file-name-2' })],
- docs: [(this.doc2 = { _id: 'doc-id-2', name: 'doc-name-2' })],
- }
- this.ProjectEntityUpdateHandler._cleanUpDoc = sinon.stub().yields()
- this.ProjectEntityUpdateHandler._cleanUpFile = sinon.stub().yields()
- const path = '/folder'
- this.newProject = 'new-project'
- this.ProjectEntityUpdateHandler._cleanUpEntity(
- this.project,
- this.newProject,
- this.folder,
- 'folder',
- path,
- userId,
- done
- )
- })
- it('should clean up all sub files', function () {
- this.ProjectEntityUpdateHandler._cleanUpFile
- .calledWith(
- this.project,
- this.file1,
- '/folder/subfolder/file-name-1',
- userId
- )
- .should.equal(true)
- this.ProjectEntityUpdateHandler._cleanUpFile
- .calledWith(this.project, this.file2, '/folder/file-name-2', userId)
- .should.equal(true)
- })
- it('should clean up all sub docs', function () {
- this.ProjectEntityUpdateHandler._cleanUpDoc
- .calledWith(
- this.project,
- this.doc1,
- '/folder/subfolder/doc-name-1',
- userId
- )
- .should.equal(true)
- this.ProjectEntityUpdateHandler._cleanUpDoc
- .calledWith(this.project, this.doc2, '/folder/doc-name-2', userId)
- .should.equal(true)
- })
- it('should should send one update to the doc updater for all docs and files', function () {
- const oldFiles = [
- { file: this.file2, path: '/folder/file-name-2' },
- { file: this.file1, path: '/folder/subfolder/file-name-1' },
- ]
- const oldDocs = [
- { doc: this.doc2, path: '/folder/doc-name-2' },
- { doc: this.doc1, path: '/folder/subfolder/doc-name-1' },
- ]
- this.DocumentUpdaterHandler.updateProjectStructure
- .calledWith(projectId, projectHistoryId, userId, {
- oldFiles,
- oldDocs,
- newProject: this.newProject,
- })
- .should.equal(true)
- })
- })
- })
- describe('_cleanUpDoc', function () {
- beforeEach(function () {
- this.doc = {
- _id: ObjectId(),
- name: 'test.tex',
- }
- this.path = '/path/to/doc'
- this.ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
- this.DocstoreManager.deleteDoc.yields()
- })
- describe('when the doc is the root doc', function () {
- beforeEach(function () {
- this.project.rootDoc_id = this.doc._id
- this.ProjectEntityUpdateHandler._cleanUpDoc(
- this.project,
- this.doc,
- this.path,
- userId,
- this.callback
- )
- })
- it('should unset the root doc', function () {
- this.ProjectEntityUpdateHandler.unsetRootDoc
- .calledWith(projectId)
- .should.equal(true)
- })
- it('should delete the doc in the doc updater', function () {
- this.DocumentUpdaterHandler.deleteDoc
- .calledWith(projectId, this.doc._id.toString())
- .should.equal(true)
- })
- it('should delete the doc in the doc store', function () {
- this.DocstoreManager.deleteDoc
- .calledWith(projectId, this.doc._id.toString(), 'test.tex')
- .should.equal(true)
- })
- it('should call the callback', function () {
- this.callback.called.should.equal(true)
- })
- })
- describe('when the doc is not the root doc', function () {
- beforeEach(function () {
- this.project.rootDoc_id = ObjectId()
- this.ProjectEntityUpdateHandler._cleanUpDoc(
- this.project,
- this.doc,
- this.path,
- userId,
- this.callback
- )
- })
- it('should not unset the root doc', function () {
- this.ProjectEntityUpdateHandler.unsetRootDoc.called.should.equal(false)
- })
- it('should call the callback', function () {
- this.callback.called.should.equal(true)
- })
- })
- })
- describe('convertDocToFile', function () {
- beforeEach(function () {
- this.docPath = '/folder/doc.tex'
- this.docLines = ['line one', 'line two']
- this.tmpFilePath = '/tmp/file'
- this.fileStoreUrl = 'http://filestore/file'
- this.folder = { _id: new ObjectId() }
- this.rev = 3
- this.ProjectLocator.findElement
- .withArgs({
- project_id: this.project._id,
- element_id: this.doc._id,
- type: 'doc',
- })
- .yields(null, this.doc, { fileSystem: this.path })
- this.ProjectLocator.findElement
- .withArgs({
- project_id: this.project._id.toString(),
- element_id: this.file._id,
- type: 'file',
- })
- .yields(null, this.file, this.docPath, this.folder)
- this.DocstoreManager.getDoc
- .withArgs(this.project._id, this.doc._id)
- .yields(null, this.docLines, this.rev)
- this.FileWriter.writeLinesToDisk.yields(null, this.tmpFilePath)
- this.FileStoreHandler.uploadFileFromDisk.yields(
- null,
- this.fileStoreUrl,
- this.file
- )
- this.ProjectEntityMongoUpdateHandler.replaceDocWithFile.yields(
- null,
- this.project
- )
- })
- describe('successfully', function () {
- beforeEach(function (done) {
- this.ProjectEntityUpdateHandler.convertDocToFile(
- this.project._id,
- this.doc._id,
- this.user._id,
- done
- )
- })
- it('deletes the document in doc updater', function () {
- expect(this.DocumentUpdaterHandler.deleteDoc).to.have.been.calledWith(
- this.project._id,
- this.doc._id
- )
- })
- it('uploads the file to filestore', function () {
- expect(
- this.FileStoreHandler.uploadFileFromDisk
- ).to.have.been.calledWith(
- this.project._id,
- { name: this.doc.name, rev: this.rev + 1 },
- this.tmpFilePath
- )
- })
- it('cleans up the temporary file', function () {
- expect(this.fs.unlink).to.have.been.calledWith(this.tmpFilePath)
- })
- it('replaces the doc with the file', function () {
- expect(
- this.ProjectEntityMongoUpdateHandler.replaceDocWithFile
- ).to.have.been.calledWith(this.project._id, this.doc._id, this.file)
- })
- it('notifies document updater of changes', function () {
- expect(
- this.DocumentUpdaterHandler.updateProjectStructure
- ).to.have.been.calledWith(
- this.project._id,
- this.project.overleaf.history.id,
- this.user._id,
- {
- oldDocs: [{ doc: this.doc, path: this.path }],
- newFiles: [
- { file: this.file, path: this.path, url: this.fileStoreUrl },
- ],
- newProject: this.project,
- }
- )
- })
- it('should notify real-time of the doc deletion', function () {
- expect(
- this.EditorRealTimeController.emitToRoom
- ).to.have.been.calledWith(
- this.project._id,
- 'removeEntity',
- this.doc._id,
- 'convertDocToFile'
- )
- })
- it('should notify real-time of the file creation', function () {
- expect(
- this.EditorRealTimeController.emitToRoom
- ).to.have.been.calledWith(
- this.project._id,
- 'reciveNewFile',
- this.folder._id,
- this.file,
- 'convertDocToFile',
- null
- )
- })
- })
- describe('when the doc has ranges', function () {
- it('should throw a DocHasRangesError', function (done) {
- this.ranges = { comments: [{ id: 123 }] }
- this.DocstoreManager.getDoc
- .withArgs(this.project._id, this.doc._id)
- .yields(null, this.docLines, 'rev', 'version', this.ranges)
- this.ProjectEntityUpdateHandler.convertDocToFile(
- this.project._id,
- this.doc._id,
- this.user._id,
- err => {
- expect(err).to.be.instanceof(Errors.DocHasRangesError)
- done()
- }
- )
- })
- })
- })
- })
|