| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289 |
- const chai = require('chai')
- const { expect } = 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(),
- 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.logger = {
- log: sinon.stub(),
- warn: sinon.stub(),
- error: sinon.stub(),
- err() {}
- }
- 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 = {
- 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(),
- _insertDeletedDocReference: 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, {
- globals: {
- console: console
- },
- requires: {
- 'logger-sharelatex': this.logger,
- fs: this.fs,
- '../../models/Doc': { Doc: this.DocModel },
- '../Docstore/DocstoreManager': this.DocstoreManager,
- '../Errors/Errors': Errors,
- '../../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.ProjectGetter.getProjectWithoutDocLines.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 without doc lines', function() {
- this.ProjectGetter.getProjectWithoutDocLines
- .calledWith(projectId)
- .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.project.deletedDocs = [{ _id: docId }]
- this.ProjectGetter.getProjectWithoutDocLines.yields(null, this.project)
- this.ProjectLocator.findElement.yields(new Errors.NotFoundError())
- 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.ProjectLocator.findElement.yields()
- 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)
- })
- })
- describe('when the project is not found', function() {
- beforeEach(function() {
- this.ProjectGetter.getProjectWithoutDocLines.yields()
- 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)
- })
- })
- })
- 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('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('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
- },
- 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}`
- }
- ]
- 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 array', 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.ProjectEntityMongoUpdateHandler._insertDeletedDocReference.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()
- )
- })
- it('should insert the doc into the deletedDocs array', function() {
- this.ProjectEntityMongoUpdateHandler._insertDeletedDocReference
- .calledWith(this.project._id, this.doc)
- .should.equal(true)
- })
- it('should delete the doc in the doc store', function() {
- this.DocstoreManager.deleteDoc
- .calledWith(projectId, this.doc._id.toString())
- .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()
- }
- )
- })
- })
- })
- })
|