ProjectEntityUpdateHandlerTests.coffee 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112
  1. chai = require('chai')
  2. assert = require('chai').assert
  3. should = chai.should()
  4. expect = chai.expect
  5. modulePath = "../../../../app/js/Features/Project/ProjectEntityUpdateHandler"
  6. sinon = require 'sinon'
  7. Errors = require "../../../../app/js/Features/Errors/Errors"
  8. SandboxedModule = require('sandboxed-module')
  9. ObjectId = require("mongoose").Types.ObjectId
  10. describe 'ProjectEntityUpdateHandler', ->
  11. project_id = '4eecb1c1bffa66588e0000a1'
  12. projectHistoryId = '123456'
  13. doc_id = '4eecb1c1bffa66588e0000a2'
  14. file_id = "4eecaffcbffa66588e000009"
  15. folder_id = "4eecaffcbffa66588e000008"
  16. rootFolderId = "4eecaffcbffa66588e000007"
  17. new_file_id = "4eecaffcbffa66588e000099"
  18. userId = 1234
  19. beforeEach ->
  20. @project =
  21. _id: project_id,
  22. name: 'project name'
  23. overleaf:
  24. history:
  25. id: projectHistoryId
  26. @fileUrl = 'filestore.example.com/file'
  27. @FileStoreHandler =
  28. uploadFileFromDisk: sinon.stub().yields(null, @fileUrl)
  29. copyFile: sinon.stub().yields(null, @fileUrl)
  30. @DocModel = class Doc
  31. constructor:(options)->
  32. {@name, @lines} = options
  33. @_id = doc_id
  34. @rev = 0
  35. @FileModel = class File
  36. constructor:(options)->
  37. {@name} = options
  38. # use a new id for replacement files
  39. if @name is 'dummy-upload-filename'
  40. @._id = new_file_id
  41. else
  42. @._id = file_id
  43. @rev = 0
  44. if options.linkedFileData?
  45. @linkedFileData = options.linkedFileData
  46. @docName = "doc-name"
  47. @docLines = ['1234','abc']
  48. @fileName = "something.jpg"
  49. @fileSystemPath = "somehintg"
  50. @linkedFileData = {provider: 'url'}
  51. @source = 'editor'
  52. @callback = sinon.stub()
  53. @ProjectEntityUpdateHandler = SandboxedModule.require modulePath, requires:
  54. 'logger-sharelatex': @logger = {log:sinon.stub(), error: sinon.stub(), err:->}
  55. '../../models/Doc': Doc:@DocModel
  56. '../Docstore/DocstoreManager': @DocstoreManager = {}
  57. '../Errors/Errors': Errors
  58. '../../Features/DocumentUpdater/DocumentUpdaterHandler':@DocumentUpdaterHandler =
  59. updateProjectStructure: sinon.stub().yields()
  60. '../../models/File': File:@FileModel
  61. '../FileStore/FileStoreHandler':@FileStoreHandler
  62. "../../infrastructure/LockManager":@LockManager =
  63. runWithLock:
  64. sinon.spy((namespace, id, runner, callback) -> runner(callback))
  65. '../../models/Project': Project:@ProjectModel = {}
  66. "./ProjectGetter": @ProjectGetter = {}
  67. './ProjectLocator': @ProjectLocator = {}
  68. './ProjectUpdateHandler': @ProjectUpdater = {}
  69. './ProjectEntityHandler': @ProjectEntityHandler = {}
  70. './ProjectEntityMongoUpdateHandler': @ProjectEntityMongoUpdateHandler = {}
  71. '../ThirdPartyDataStore/TpdsUpdateSender':@TpdsUpdateSender =
  72. addFile: sinon.stub().yields()
  73. describe 'copyFileFromExistingProjectWithProject', ->
  74. beforeEach ->
  75. @oldProject_id = "123kljadas"
  76. @oldFileRef = {name:@fileName, _id:"oldFileRef"}
  77. @ProjectEntityMongoUpdateHandler._confirmFolder = sinon.stub().yields(folder_id)
  78. @ProjectEntityMongoUpdateHandler._putElement = sinon.stub().yields(null, {path:{fileSystem: @fileSystemPath}})
  79. @ProjectEntityUpdateHandler.copyFileFromExistingProjectWithProject @project._id, @project, folder_id, @oldProject_id, @oldFileRef, userId, @callback
  80. it 'should copy the file in FileStoreHandler', ->
  81. @FileStoreHandler.copyFile
  82. .calledWith(@oldProject_id, @oldFileRef._id, project_id, file_id)
  83. .should.equal true
  84. it 'should put file into folder by calling put element', ->
  85. @ProjectEntityMongoUpdateHandler._putElement
  86. .calledWithMatch(@project, folder_id, { _id: file_id, name: @fileName }, "file")
  87. .should.equal true
  88. it 'should return doc and parent folder', ->
  89. @callback.calledWithMatch(null,{ _id: file_id, name: @fileName }, folder_id).should.equal true
  90. it 'should call third party data store if versioning is enabled', ->
  91. @TpdsUpdateSender.addFile.calledWith(
  92. project_id: project_id
  93. file_id: file_id
  94. path: @fileSystemPath
  95. rev: 0
  96. project_name: @project.name
  97. ).should.equal true
  98. it "should should send the change in project structure to the doc updater", ->
  99. changesMatcher = sinon.match (changes) =>
  100. { newFiles } = changes
  101. return false if newFiles.length != 1
  102. newFile = newFiles[0]
  103. newFile.file._id == file_id &&
  104. newFile.path == @fileSystemPath &&
  105. newFile.url == @fileUrl
  106. @DocumentUpdaterHandler.updateProjectStructure
  107. .calledWithMatch(project_id, projectHistoryId, userId, changesMatcher)
  108. .should.equal true
  109. describe 'copyFileFromExistingProjectWithProject, with linkedFileData', ->
  110. beforeEach ->
  111. @oldProject_id = "123kljadas"
  112. @oldFileRef = {
  113. _id:"oldFileRef",
  114. name:@fileName,
  115. linkedFileData: @linkedFileData
  116. }
  117. @ProjectEntityMongoUpdateHandler._confirmFolder = sinon.stub().yields(folder_id)
  118. @ProjectEntityMongoUpdateHandler._putElement = sinon.stub().yields(null, {path:{fileSystem: @fileSystemPath}})
  119. @ProjectEntityUpdateHandler.copyFileFromExistingProjectWithProject @project._id, @project, folder_id, @oldProject_id, @oldFileRef, userId, @callback
  120. it 'should copy the file in FileStoreHandler', ->
  121. @FileStoreHandler.copyFile
  122. .calledWith(@oldProject_id, @oldFileRef._id, project_id, file_id)
  123. .should.equal true
  124. it 'should put file into folder by calling put element, with the linkedFileData', ->
  125. @ProjectEntityMongoUpdateHandler._putElement
  126. .calledWithMatch(
  127. @project,
  128. folder_id,
  129. { _id: file_id, name: @fileName, linkedFileData: @linkedFileData},
  130. "file"
  131. )
  132. .should.equal true
  133. describe 'updateDocLines', ->
  134. beforeEach ->
  135. @path = "/somewhere/something.tex"
  136. @doc = {
  137. _id: doc_id
  138. }
  139. @version = 42
  140. @ranges = {"mock":"ranges"}
  141. @lastUpdatedAt = (new Date()).getTime()
  142. @lastUpdatedBy = 'fake-last-updater-id'
  143. @ProjectGetter.getProjectWithoutDocLines = sinon.stub().yields(null, @project)
  144. @ProjectLocator.findElement = sinon.stub().yields(null, @doc, {fileSystem: @path})
  145. @TpdsUpdateSender.addDoc = sinon.stub().yields()
  146. @ProjectUpdater.markAsUpdated = sinon.stub()
  147. @callback = sinon.stub()
  148. describe "when the doc has been modified", ->
  149. beforeEach ->
  150. @DocstoreManager.updateDoc = sinon.stub().yields(null, true, @rev = 5)
  151. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback
  152. it "should get the project without doc lines", ->
  153. @ProjectGetter.getProjectWithoutDocLines
  154. .calledWith(project_id)
  155. .should.equal true
  156. it "should find the doc", ->
  157. @ProjectLocator.findElement
  158. .calledWith({
  159. project: @project
  160. type: "docs"
  161. element_id: doc_id
  162. })
  163. .should.equal true
  164. it "should update the doc in the docstore", ->
  165. @DocstoreManager.updateDoc
  166. .calledWith(project_id, doc_id, @docLines, @version, @ranges)
  167. .should.equal true
  168. it "should mark the project as updated", ->
  169. sinon.assert.calledWith(
  170. @ProjectUpdater.markAsUpdated,
  171. project_id,
  172. @lastUpdatedAt,
  173. @lastUpdatedBy
  174. )
  175. it "should send the doc the to the TPDS", ->
  176. @TpdsUpdateSender.addDoc
  177. .calledWith({
  178. project_id: project_id
  179. project_name: @project.name
  180. doc_id: doc_id
  181. rev: @rev
  182. path: @path
  183. })
  184. .should.equal true
  185. it "should call the callback", ->
  186. @callback.called.should.equal true
  187. describe "when the doc has not been modified", ->
  188. beforeEach ->
  189. @DocstoreManager.updateDoc = sinon.stub().yields(null, false, @rev = 5)
  190. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback
  191. it "should not mark the project as updated", ->
  192. @ProjectUpdater.markAsUpdated.called.should.equal false
  193. it "should not send the doc the to the TPDS", ->
  194. @TpdsUpdateSender.addDoc.called.should.equal false
  195. it "should call the callback", ->
  196. @callback.called.should.equal true
  197. describe "when the doc has been deleted", ->
  198. beforeEach ->
  199. @project.deletedDocs = [ _id: doc_id ]
  200. @ProjectGetter.getProjectWithoutDocLines = sinon.stub().yields(null, @project)
  201. @ProjectLocator.findElement = sinon.stub().yields(new Errors.NotFoundError)
  202. @DocstoreManager.updateDoc = sinon.stub().yields()
  203. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback
  204. it "should update the doc in the docstore", ->
  205. @DocstoreManager.updateDoc
  206. .calledWith(project_id, doc_id, @docLines, @version, @ranges)
  207. .should.equal true
  208. it "should not mark the project as updated", ->
  209. @ProjectUpdater.markAsUpdated.called.should.equal false
  210. it "should not send the doc the to the TPDS", ->
  211. @TpdsUpdateSender.addDoc.called.should.equal false
  212. it "should call the callback", ->
  213. @callback.called.should.equal true
  214. describe "when the doc is not related to the project", ->
  215. beforeEach ->
  216. @ProjectLocator.findElement = sinon.stub().yields()
  217. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback
  218. it "should log out the error", ->
  219. @logger.error
  220. .calledWith(
  221. project_id: project_id
  222. doc_id: doc_id
  223. lines: @docLines
  224. "doc not found while updating doc lines"
  225. )
  226. .should.equal true
  227. it "should return a not found error", ->
  228. @callback.calledWith(new Errors.NotFoundError()).should.equal true
  229. describe "when the project is not found", ->
  230. beforeEach ->
  231. @ProjectGetter.getProjectWithoutDocLines = sinon.stub().yields()
  232. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback
  233. it "should return a not found error", ->
  234. @callback.calledWith(new Errors.NotFoundError()).should.equal true
  235. describe "setRootDoc", ->
  236. it "should call Project.update", ->
  237. rootDoc_id = "root-doc-id-123123"
  238. @ProjectModel.update = sinon.stub()
  239. @ProjectEntityUpdateHandler.setRootDoc project_id, rootDoc_id
  240. @ProjectModel.update
  241. .calledWith({_id : project_id}, {rootDoc_id})
  242. .should.equal true
  243. describe "unsetRootDoc", ->
  244. it "should call Project.update", ->
  245. @ProjectModel.update = sinon.stub()
  246. @ProjectEntityUpdateHandler.unsetRootDoc project_id
  247. @ProjectModel.update
  248. .calledWith({_id : project_id}, {$unset : {rootDoc_id: true}})
  249. .should.equal true
  250. describe 'addDoc', ->
  251. describe 'adding a doc', ->
  252. beforeEach ->
  253. @path = "/path/to/doc"
  254. @newDoc = name:@docName, lines:undefined, _id: doc_id, rev: 0
  255. @DocstoreManager.updateDoc = sinon.stub().yields(null, false, @rev = 5)
  256. @TpdsUpdateSender.addDoc = sinon.stub().yields()
  257. @ProjectEntityMongoUpdateHandler.addDoc = sinon.stub().yields(null, {path: fileSystem: @path}, @project)
  258. @ProjectEntityUpdateHandler.addDoc project_id, doc_id, @docName, @docLines, userId, @callback
  259. it "creates the doc without history", () ->
  260. @DocstoreManager.updateDoc
  261. .calledWith(project_id, doc_id, @docLines, 0, {})
  262. .should.equal true
  263. it "sends the change in project structure to the doc updater", () ->
  264. newDocs = [
  265. doc: @newDoc
  266. path: @path
  267. docLines: @docLines.join('\n')
  268. ]
  269. @DocumentUpdaterHandler.updateProjectStructure
  270. .calledWith(project_id, projectHistoryId, userId, {newDocs, newProject: @project})
  271. .should.equal true
  272. describe 'adding a doc with an invalid name', ->
  273. beforeEach ->
  274. @path = "/path/to/doc"
  275. @newDoc = _id: doc_id
  276. @ProjectEntityUpdateHandler.addDoc project_id, folder_id, "*" + @docName, @docLines, userId, @callback
  277. it 'returns an error', ->
  278. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  279. @callback.calledWithMatch(errorMatcher)
  280. .should.equal true
  281. describe 'addFile', ->
  282. describe 'adding a file', ->
  283. beforeEach ->
  284. @path = "/path/to/file"
  285. @newFile = {_id: file_id, rev: 0, name: @fileName, linkedFileData: @linkedFileData}
  286. @TpdsUpdateSender.addFile = sinon.stub().yields()
  287. @ProjectEntityMongoUpdateHandler.addFile = sinon.stub().yields(null, {path: fileSystem: @path}, @project)
  288. @ProjectEntityUpdateHandler.addFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback
  289. it "updates the file in the filestore", () ->
  290. @FileStoreHandler.uploadFileFromDisk
  291. .calledWith(project_id, file_id, @fileSystemPath)
  292. .should.equal true
  293. it "updates the file in mongo", () ->
  294. fileMatcher = sinon.match (file) =>
  295. file.name == @fileName
  296. @ProjectEntityMongoUpdateHandler.addFile
  297. .calledWithMatch(project_id, folder_id, fileMatcher)
  298. .should.equal true
  299. it "notifies the tpds", () ->
  300. @TpdsUpdateSender.addFile
  301. .calledWith({
  302. project_id: project_id
  303. project_name: @project.name
  304. file_id: file_id
  305. rev: 0
  306. path: @path
  307. })
  308. .should.equal true
  309. it "sends the change in project structure to the doc updater", () ->
  310. newFiles = [
  311. file: @newFile
  312. path: @path
  313. url: @fileUrl
  314. ]
  315. @DocumentUpdaterHandler.updateProjectStructure
  316. .calledWith(project_id, projectHistoryId, userId, {newFiles, newProject: @project})
  317. .should.equal true
  318. describe 'adding a file with an invalid name', ->
  319. beforeEach ->
  320. @path = "/path/to/file"
  321. @newFile = {_id: file_id, rev: 0, name: @fileName, linkedFileData: @linkedFileData}
  322. @TpdsUpdateSender.addFile = sinon.stub().yields()
  323. @ProjectEntityMongoUpdateHandler.addFile = sinon.stub().yields(null, {path: fileSystem: @path}, @project)
  324. @ProjectEntityUpdateHandler.addFile project_id, folder_id, "*" + @fileName, @fileSystemPath, @linkedFileData, userId, @callback
  325. it 'returns an error', ->
  326. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  327. @callback.calledWithMatch(errorMatcher)
  328. .should.equal true
  329. describe 'replaceFile', ->
  330. beforeEach ->
  331. # replacement file now creates a new file object
  332. @newFileUrl = "new-file-url"
  333. @FileStoreHandler.uploadFileFromDisk = sinon.stub().yields(null, @newFileUrl)
  334. @newFile = _id: new_file_id, name: "dummy-upload-filename", rev: 0, linkedFileData: @linkedFileData
  335. @oldFile = _id: file_id, rev: 3
  336. @path = "/path/to/file"
  337. @newProject = "new project"
  338. @ProjectEntityMongoUpdateHandler._insertDeletedFileReference = sinon.stub().yields()
  339. @ProjectEntityMongoUpdateHandler.replaceFileWithNew = sinon.stub().yields(null, @oldFile, @project, fileSystem: @path, @newProject)
  340. @ProjectEntityUpdateHandler.replaceFile project_id, file_id, @fileSystemPath, @linkedFileData, userId, @callback
  341. it 'uploads a new version of the file', ->
  342. @FileStoreHandler.uploadFileFromDisk
  343. .calledWith(project_id, new_file_id, @fileSystemPath)
  344. .should.equal true
  345. it 'replaces the file in mongo', ->
  346. @ProjectEntityMongoUpdateHandler.replaceFileWithNew
  347. .calledWith(project_id, file_id, @newFile)
  348. .should.equal true
  349. it 'notifies the tpds', ->
  350. @TpdsUpdateSender.addFile
  351. .calledWith({
  352. project_id: project_id
  353. project_name: @project.name
  354. file_id: new_file_id
  355. rev: @oldFile.rev + 1
  356. path: @path
  357. })
  358. .should.equal true
  359. it 'updates the project structure in the doc updater', ->
  360. oldFiles = [
  361. file: @oldFile
  362. path: @path
  363. ]
  364. newFiles = [
  365. file: @newFile
  366. path: @path
  367. url: @newFileUrl
  368. ]
  369. @DocumentUpdaterHandler.updateProjectStructure
  370. .calledWith(project_id, projectHistoryId, userId, {oldFiles, newFiles, newProject: @newProject})
  371. .should.equal true
  372. describe 'upsertDoc', ->
  373. describe 'upserting into an invalid folder', ->
  374. beforeEach ->
  375. @ProjectLocator.findElement = sinon.stub().yields()
  376. @ProjectEntityUpdateHandler.upsertDoc project_id, folder_id, @docName, @docLines, @source, userId, @callback
  377. it 'returns an error', ->
  378. errorMatcher = sinon.match.instanceOf(Error)
  379. @callback.calledWithMatch(errorMatcher)
  380. .should.equal true
  381. describe 'updating an existing doc', ->
  382. beforeEach ->
  383. @existingDoc = _id: doc_id, name: @docName
  384. @folder = _id: folder_id, docs: [@existingDoc]
  385. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  386. @DocumentUpdaterHandler.setDocument = sinon.stub().yields()
  387. @DocumentUpdaterHandler.flushDocToMongo = sinon.stub().yields()
  388. @ProjectEntityUpdateHandler.upsertDoc project_id, folder_id, @docName, @docLines, @source, userId, @callback
  389. it 'tries to find the folder', ->
  390. @ProjectLocator.findElement
  391. .calledWith({project_id, element_id: folder_id, type: "folder"})
  392. .should.equal true
  393. it 'updates the doc contents', ->
  394. @DocumentUpdaterHandler.setDocument
  395. .calledWith(project_id, @existingDoc._id, userId, @docLines, @source)
  396. .should.equal true
  397. it 'flushes the doc contents', ->
  398. @DocumentUpdaterHandler.flushDocToMongo
  399. .calledWith(project_id, @existingDoc._id )
  400. .should.equal true
  401. it 'returns the doc', ->
  402. @callback.calledWith(null, @existingDoc, false)
  403. describe 'creating a new doc', ->
  404. beforeEach ->
  405. @folder = _id: folder_id, docs: []
  406. @newDoc = _id: doc_id
  407. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  408. @ProjectEntityUpdateHandler.addDocWithRanges = withoutLock: sinon.stub().yields(null, @newDoc)
  409. @ProjectEntityUpdateHandler.upsertDoc project_id, folder_id, @docName, @docLines, @source, userId, @callback
  410. it 'tries to find the folder', ->
  411. @ProjectLocator.findElement
  412. .calledWith({project_id, element_id: folder_id, type: "folder"})
  413. .should.equal true
  414. it 'adds the doc', ->
  415. @ProjectEntityUpdateHandler.addDocWithRanges.withoutLock
  416. .calledWith(project_id, folder_id, @docName, @docLines, {}, userId)
  417. .should.equal true
  418. it 'returns the doc', ->
  419. @callback.calledWith(null, @newDoc, true)
  420. describe 'upserting a new doc with an invalid name', ->
  421. beforeEach ->
  422. @folder = _id: folder_id, docs: []
  423. @newDoc = _id: doc_id
  424. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  425. @ProjectEntityUpdateHandler.addDocWithRanges = withoutLock: sinon.stub().yields(null, @newDoc)
  426. @ProjectEntityUpdateHandler.upsertDoc project_id, folder_id, "*" + @docName, @docLines, @source, userId, @callback
  427. it 'returns an error', ->
  428. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  429. @callback.calledWithMatch(errorMatcher)
  430. .should.equal true
  431. describe 'upsertFile', ->
  432. describe 'upserting into an invalid folder', ->
  433. beforeEach ->
  434. @ProjectLocator.findElement = sinon.stub().yields()
  435. @ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback
  436. it 'returns an error', ->
  437. errorMatcher = sinon.match.instanceOf(Error)
  438. @callback.calledWithMatch(errorMatcher)
  439. .should.equal true
  440. describe 'updating an existing file', ->
  441. beforeEach ->
  442. @existingFile = _id: file_id, name: @fileName
  443. @folder = _id: folder_id, fileRefs: [@existingFile]
  444. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  445. @ProjectEntityUpdateHandler.replaceFile = mainTask: sinon.stub().yields(null, @newFile)
  446. @ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback
  447. it 'replaces the file', ->
  448. @ProjectEntityUpdateHandler.replaceFile.mainTask
  449. .calledWith(project_id, file_id, @fileSystemPath, @linkedFileData, userId)
  450. .should.equal true
  451. it 'returns the file', ->
  452. @callback.calledWith(null, @existingFile, false)
  453. describe 'creating a new file', ->
  454. beforeEach ->
  455. @folder = _id: folder_id, fileRefs: []
  456. @newFile = _id: file_id
  457. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  458. @ProjectEntityUpdateHandler.addFile = mainTask: sinon.stub().yields(null, @newFile)
  459. @ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback
  460. it 'tries to find the folder', ->
  461. @ProjectLocator.findElement
  462. .calledWith({project_id, element_id: folder_id, type: "folder"})
  463. .should.equal true
  464. it 'adds the file', ->
  465. @ProjectEntityUpdateHandler.addFile.mainTask
  466. .calledWith(project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId)
  467. .should.equal true
  468. it 'returns the file', ->
  469. @callback.calledWith(null, @newFile, true)
  470. describe 'upserting a new file with an invalid name', ->
  471. beforeEach ->
  472. @folder = _id: folder_id, fileRefs: []
  473. @newFile = _id: file_id
  474. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  475. @ProjectEntityUpdateHandler.addFile = mainTask: sinon.stub().yields(null, @newFile)
  476. @ProjectEntityUpdateHandler.upsertFile project_id, folder_id, '*' + @fileName, @fileSystemPath, @linkedFileData, userId, @callback
  477. it 'returns an error', ->
  478. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  479. @callback.calledWithMatch(errorMatcher)
  480. .should.equal true
  481. describe 'upsertDocWithPath', ->
  482. describe 'upserting a doc', ->
  483. beforeEach ->
  484. @path = "/folder/doc.tex"
  485. @newFolders = [ 'mock-a', 'mock-b' ]
  486. @folder = _id: folder_id
  487. @doc = _id: doc_id
  488. @isNewDoc = true
  489. @ProjectEntityUpdateHandler.mkdirp =
  490. withoutLock: sinon.stub().yields(null, @newFolders, @folder)
  491. @ProjectEntityUpdateHandler.upsertDoc =
  492. withoutLock: sinon.stub().yields(null, @doc, @isNewDoc)
  493. @ProjectEntityUpdateHandler.upsertDocWithPath project_id, @path, @docLines, @source, userId, @callback
  494. it 'creates any necessary folders', ->
  495. @ProjectEntityUpdateHandler.mkdirp.withoutLock
  496. .calledWith(project_id, '/folder')
  497. .should.equal true
  498. it 'upserts the doc', ->
  499. @ProjectEntityUpdateHandler.upsertDoc.withoutLock
  500. .calledWith(project_id, @folder._id, 'doc.tex', @docLines, @source, userId)
  501. .should.equal true
  502. it 'calls the callback', ->
  503. @callback
  504. .calledWith(null, @doc, @isNewDoc, @newFolders, @folder)
  505. .should.equal true
  506. describe 'upserting a doc with an invalid path', ->
  507. beforeEach ->
  508. @path = "/*folder/doc.tex"
  509. @newFolders = [ 'mock-a', 'mock-b' ]
  510. @folder = _id: folder_id
  511. @doc = _id: doc_id
  512. @isNewDoc = true
  513. @ProjectEntityUpdateHandler.mkdirp =
  514. withoutLock: sinon.stub().yields(null, @newFolders, @folder)
  515. @ProjectEntityUpdateHandler.upsertDoc =
  516. withoutLock: sinon.stub().yields(null, @doc, @isNewDoc)
  517. @ProjectEntityUpdateHandler.upsertDocWithPath project_id, @path, @docLines, @source, userId, @callback
  518. it 'returns an error', ->
  519. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  520. @callback.calledWithMatch(errorMatcher)
  521. .should.equal true
  522. describe 'upserting a doc with an invalid name', ->
  523. beforeEach ->
  524. @path = "/folder/*doc.tex"
  525. @newFolders = [ 'mock-a', 'mock-b' ]
  526. @folder = _id: folder_id
  527. @doc = _id: doc_id
  528. @isNewDoc = true
  529. @ProjectEntityUpdateHandler.mkdirp =
  530. withoutLock: sinon.stub().yields(null, @newFolders, @folder)
  531. @ProjectEntityUpdateHandler.upsertDoc =
  532. withoutLock: sinon.stub().yields(null, @doc, @isNewDoc)
  533. @ProjectEntityUpdateHandler.upsertDocWithPath project_id, @path, @docLines, @source, userId, @callback
  534. it 'returns an error', ->
  535. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  536. @callback.calledWithMatch(errorMatcher)
  537. .should.equal true
  538. describe 'upsertFileWithPath', ->
  539. describe 'upserting a file', ->
  540. beforeEach ->
  541. @path = "/folder/file.png"
  542. @newFolders = [ 'mock-a', 'mock-b' ]
  543. @folder = _id: folder_id
  544. @file = _id: file_id
  545. @isNewFile = true
  546. @ProjectEntityUpdateHandler.mkdirp =
  547. withoutLock: sinon.stub().yields(null, @newFolders, @folder)
  548. @ProjectEntityUpdateHandler.upsertFile =
  549. mainTask: sinon.stub().yields(null, @file, @isNewFile)
  550. @ProjectEntityUpdateHandler.upsertFileWithPath project_id, @path, @fileSystemPath, @linkedFileData, userId, @callback
  551. it 'creates any necessary folders', ->
  552. @ProjectEntityUpdateHandler.mkdirp.withoutLock
  553. .calledWith(project_id, '/folder')
  554. .should.equal true
  555. it 'upserts the file', ->
  556. @ProjectEntityUpdateHandler.upsertFile.mainTask
  557. .calledWith(project_id, @folder._id, 'file.png', @fileSystemPath, @linkedFileData, userId)
  558. .should.equal true
  559. it 'calls the callback', ->
  560. @callback
  561. .calledWith(null, @file, @isNewFile, undefined, @newFolders, @folder)
  562. .should.equal true
  563. describe 'upserting a file with an invalid path', ->
  564. beforeEach ->
  565. @path = "/*folder/file.png"
  566. @newFolders = [ 'mock-a', 'mock-b' ]
  567. @folder = _id: folder_id
  568. @file = _id: file_id
  569. @isNewFile = true
  570. @ProjectEntityUpdateHandler.mkdirp =
  571. withoutLock: sinon.stub().yields(null, @newFolders, @folder)
  572. @ProjectEntityUpdateHandler.upsertFile =
  573. mainTask: sinon.stub().yields(null, @file, @isNewFile)
  574. @ProjectEntityUpdateHandler.upsertFileWithPath project_id, @path, @fileSystemPath, @linkedFileData, userId, @callback
  575. it 'returns an error', ->
  576. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  577. @callback.calledWithMatch(errorMatcher)
  578. .should.equal true
  579. describe 'upserting a file with an invalid name', ->
  580. beforeEach ->
  581. @path = "/folder/*file.png"
  582. @newFolders = [ 'mock-a', 'mock-b' ]
  583. @folder = _id: folder_id
  584. @file = _id: file_id
  585. @isNewFile = true
  586. @ProjectEntityUpdateHandler.mkdirp =
  587. withoutLock: sinon.stub().yields(null, @newFolders, @folder)
  588. @ProjectEntityUpdateHandler.upsertFile =
  589. mainTask: sinon.stub().yields(null, @file, @isNewFile)
  590. @ProjectEntityUpdateHandler.upsertFileWithPath project_id, @path, @fileSystemPath, @linkedFileData, userId, @callback
  591. it 'returns an error', ->
  592. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  593. @callback.calledWithMatch(errorMatcher)
  594. .should.equal true
  595. describe 'deleteEntity', ->
  596. beforeEach ->
  597. @path = '/path/to/doc.tex'
  598. @doc = _id: doc_id
  599. @projectBeforeDeletion = _id: project_id, name: 'project'
  600. @newProject = "new-project"
  601. @ProjectEntityMongoUpdateHandler.deleteEntity = sinon.stub().yields(null, @doc, {fileSystem: @path}, @projectBeforeDeletion, @newProject)
  602. @ProjectEntityUpdateHandler._cleanUpEntity = sinon.stub().yields()
  603. @TpdsUpdateSender.deleteEntity = sinon.stub().yields()
  604. @ProjectEntityUpdateHandler.deleteEntity project_id, doc_id, 'doc', userId, @callback
  605. it 'deletes the entity in mongo', ->
  606. @ProjectEntityMongoUpdateHandler.deleteEntity
  607. .calledWith(project_id, doc_id, 'doc')
  608. .should.equal true
  609. it 'cleans up the doc in the docstore', ->
  610. @ProjectEntityUpdateHandler._cleanUpEntity
  611. .calledWith(@projectBeforeDeletion, @newProject, @doc, 'doc', @path, userId)
  612. .should.equal true
  613. it 'it notifies the tpds', ->
  614. @TpdsUpdateSender.deleteEntity
  615. .calledWith({ project_id, @path, project_name: @projectBeforeDeletion.name })
  616. .should.equal true
  617. it 'retuns the entity_id', ->
  618. @callback.calledWith(null, doc_id).should.equal true
  619. describe 'deleteEntityWithPath', ->
  620. describe 'when the entity exists', ->
  621. beforeEach ->
  622. @doc = _id: doc_id
  623. @ProjectLocator.findElementByPath = sinon.stub().yields(null, @doc, 'doc')
  624. @ProjectEntityUpdateHandler.deleteEntity =
  625. withoutLock: sinon.stub().yields()
  626. @path = '/path/to/doc.tex'
  627. @ProjectEntityUpdateHandler.deleteEntityWithPath project_id, @path, userId, @callback
  628. it 'finds the entity', ->
  629. @ProjectLocator.findElementByPath
  630. .calledWith({project_id, @path})
  631. .should.equal true
  632. it 'deletes the entity', ->
  633. @ProjectEntityUpdateHandler.deleteEntity.withoutLock
  634. .calledWith(project_id, @doc._id, 'doc', userId, @callback)
  635. .should.equal true
  636. describe 'when the entity does not exist', ->
  637. beforeEach ->
  638. @ProjectLocator.findElementByPath = sinon.stub().yields()
  639. @path = '/doc.tex'
  640. @ProjectEntityUpdateHandler.deleteEntityWithPath project_id, @path, userId, @callback
  641. it 'returns an error', ->
  642. @callback.calledWith(new Errors.NotFoundError()).should.equal true
  643. describe 'mkdirp', ->
  644. beforeEach ->
  645. @docPath = '/folder/doc.tex'
  646. @ProjectEntityMongoUpdateHandler.mkdirp = sinon.stub().yields()
  647. @ProjectEntityUpdateHandler.mkdirp project_id, @docPath, @callback
  648. it 'calls ProjectEntityMongoUpdateHandler', ->
  649. @ProjectEntityMongoUpdateHandler.mkdirp
  650. .calledWith(project_id, @docPath)
  651. .should.equal true
  652. describe 'mkdirpWithExactCase', ->
  653. beforeEach ->
  654. @docPath = '/folder/doc.tex'
  655. @ProjectEntityMongoUpdateHandler.mkdirp = sinon.stub().yields()
  656. @ProjectEntityUpdateHandler.mkdirpWithExactCase project_id, @docPath, @callback
  657. it 'calls ProjectEntityMongoUpdateHandler', ->
  658. @ProjectEntityMongoUpdateHandler.mkdirp
  659. .calledWith(project_id, @docPath, {exactCaseMatch: true})
  660. .should.equal true
  661. describe 'addFolder', ->
  662. describe 'adding a folder', ->
  663. beforeEach ->
  664. @parentFolder_id = '123asdf'
  665. @folderName = 'new-folder'
  666. @ProjectEntityMongoUpdateHandler.addFolder = sinon.stub().yields()
  667. @ProjectEntityUpdateHandler.addFolder project_id, @parentFolder_id, @folderName, @callback
  668. it 'calls ProjectEntityMongoUpdateHandler', ->
  669. @ProjectEntityMongoUpdateHandler.addFolder
  670. .calledWith(project_id, @parentFolder_id, @folderName)
  671. .should.equal true
  672. describe 'adding a folder with an invalid name', ->
  673. beforeEach ->
  674. @parentFolder_id = '123asdf'
  675. @folderName = '*new-folder'
  676. @ProjectEntityMongoUpdateHandler.addFolder = sinon.stub().yields()
  677. @ProjectEntityUpdateHandler.addFolder project_id, @parentFolder_id, @folderName, @callback
  678. it 'returns an error', ->
  679. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  680. @callback.calledWithMatch(errorMatcher)
  681. .should.equal true
  682. describe 'moveEntity', ->
  683. beforeEach ->
  684. @project_name = 'project name'
  685. @startPath = '/a.tex'
  686. @endPath = '/folder/b.tex'
  687. @rev = 2
  688. @changes = newDocs: ['old-doc'], newFiles: ['old-file']
  689. @ProjectEntityMongoUpdateHandler.moveEntity = sinon.stub().yields(
  690. null, @project, @startPath, @endPath, @rev, @changes
  691. )
  692. @TpdsUpdateSender.moveEntity = sinon.stub()
  693. @DocumentUpdaterHandler.updateProjectStructure = sinon.stub()
  694. @ProjectEntityUpdateHandler.moveEntity project_id, doc_id, folder_id, 'doc', userId, @callback
  695. it 'moves the entity in mongo', ->
  696. @ProjectEntityMongoUpdateHandler.moveEntity
  697. .calledWith(project_id, doc_id, folder_id, 'doc')
  698. .should.equal true
  699. it 'notifies tpds', ->
  700. @TpdsUpdateSender.moveEntity
  701. .calledWith({project_id, @project_name, @startPath, @endPath, @rev})
  702. .should.equal true
  703. it 'sends the changes in project structure to the doc updater', ->
  704. @DocumentUpdaterHandler.updateProjectStructure
  705. .calledWith(project_id, projectHistoryId, userId, @changes, @callback)
  706. .should.equal true
  707. describe "renameEntity", ->
  708. describe 'renaming an entity', ->
  709. beforeEach ->
  710. @project_name = 'project name'
  711. @startPath = '/folder/a.tex'
  712. @endPath = '/folder/b.tex'
  713. @rev = 2
  714. @changes = newDocs: ['old-doc'], newFiles: ['old-file']
  715. @newDocName = 'b.tex'
  716. @ProjectEntityMongoUpdateHandler.renameEntity = sinon.stub().yields(
  717. null, @project, @startPath, @endPath, @rev, @changes
  718. )
  719. @TpdsUpdateSender.moveEntity = sinon.stub()
  720. @DocumentUpdaterHandler.updateProjectStructure = sinon.stub()
  721. @ProjectEntityUpdateHandler.renameEntity project_id, doc_id, 'doc', @newDocName, userId, @callback
  722. it 'moves the entity in mongo', ->
  723. @ProjectEntityMongoUpdateHandler.renameEntity
  724. .calledWith(project_id, doc_id, 'doc', @newDocName)
  725. .should.equal true
  726. it 'notifies tpds', ->
  727. @TpdsUpdateSender.moveEntity
  728. .calledWith({project_id, @project_name, @startPath, @endPath, @rev})
  729. .should.equal true
  730. it 'sends the changes in project structure to the doc updater', ->
  731. @DocumentUpdaterHandler.updateProjectStructure
  732. .calledWith(project_id, projectHistoryId, userId, @changes, @callback)
  733. .should.equal true
  734. describe 'renaming an entity to an invalid name', ->
  735. beforeEach ->
  736. @project_name = 'project name'
  737. @startPath = '/folder/a.tex'
  738. @endPath = '/folder/b.tex'
  739. @rev = 2
  740. @changes = newDocs: ['old-doc'], newFiles: ['old-file']
  741. @newDocName = '*b.tex'
  742. @ProjectEntityMongoUpdateHandler.renameEntity = sinon.stub().yields(
  743. null, @project, @startPath, @endPath, @rev, @changes
  744. )
  745. @TpdsUpdateSender.moveEntity = sinon.stub()
  746. @DocumentUpdaterHandler.updateProjectStructure = sinon.stub()
  747. @ProjectEntityUpdateHandler.renameEntity project_id, doc_id, 'doc', @newDocName, userId, @callback
  748. it 'returns an error', ->
  749. errorMatcher = sinon.match.instanceOf(Errors.InvalidNameError)
  750. @callback.calledWithMatch(errorMatcher)
  751. .should.equal true
  752. describe "resyncProjectHistory", ->
  753. describe "a deleted project", ->
  754. beforeEach ->
  755. @ProjectGetter.getProject = sinon.stub().yields()
  756. @ProjectEntityUpdateHandler.resyncProjectHistory project_id, @callback
  757. it "should return an error", ->
  758. error = new Errors.ProjectHistoryDisabledError("project history not enabled for #{project_id}")
  759. @callback.calledWith(error).should.equal true
  760. describe "a project without project-history enabled", ->
  761. beforeEach ->
  762. @project.overleaf = {}
  763. @ProjectGetter.getProject = sinon.stub().yields(null, @project)
  764. @ProjectEntityUpdateHandler.resyncProjectHistory project_id, @callback
  765. it "should return an error", ->
  766. error = new Errors.ProjectHistoryDisabledError("project history not enabled for #{project_id}")
  767. @callback.calledWith(error).should.equal true
  768. describe "a project with project-history enabled", ->
  769. beforeEach ->
  770. @ProjectGetter.getProject = sinon.stub().yields(null, @project)
  771. docs = [
  772. doc: _id: doc_id
  773. path: 'main.tex'
  774. ]
  775. files = [
  776. file: _id: file_id
  777. path: 'universe.png'
  778. ]
  779. @ProjectEntityHandler.getAllEntitiesFromProject = sinon.stub().yields(null, docs, files)
  780. @FileStoreHandler._buildUrl = (project_id, file_id) ->
  781. "www.filestore.test/#{project_id}/#{file_id}"
  782. @DocumentUpdaterHandler.resyncProjectHistory = sinon.stub().yields()
  783. @ProjectEntityUpdateHandler.resyncProjectHistory project_id, @callback
  784. it 'gets the project', ->
  785. @ProjectGetter.getProject
  786. .calledWith(project_id)
  787. .should.equal true
  788. it 'gets the entities for the project', ->
  789. @ProjectEntityHandler.getAllEntitiesFromProject
  790. .calledWith(@project)
  791. .should.equal true
  792. it 'tells the doc updater to sync the project', ->
  793. docs = [
  794. doc: doc_id
  795. path: 'main.tex'
  796. ]
  797. files = [
  798. file: file_id
  799. path: 'universe.png'
  800. url: "www.filestore.test/#{project_id}/#{file_id}"
  801. ]
  802. @DocumentUpdaterHandler.resyncProjectHistory
  803. .calledWith(project_id, projectHistoryId, docs, files)
  804. .should.equal true
  805. it 'calls the callback', ->
  806. @callback.called.should.equal true
  807. describe "_cleanUpEntity", ->
  808. beforeEach ->
  809. @entity_id = "4eecaffcbffa66588e000009"
  810. @FileStoreHandler.deleteFile = sinon.stub().yields()
  811. @DocumentUpdaterHandler.deleteDoc = sinon.stub().yields()
  812. @ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
  813. @ProjectEntityMongoUpdateHandler._insertDeletedFileReference = sinon.stub().yields()
  814. describe "a file", ->
  815. beforeEach (done) ->
  816. @path = "/file/system/path.png"
  817. @entity = _id: @entity_id
  818. @newProject = "new-project"
  819. @ProjectEntityUpdateHandler._cleanUpEntity @project, @newProject, @entity, 'file', @path, userId, done
  820. it "should insert the file into the deletedFiles array", ->
  821. @ProjectEntityMongoUpdateHandler._insertDeletedFileReference
  822. .calledWith(@project._id, @entity)
  823. .should.equal true
  824. it "should not delete the file from FileStoreHandler", ->
  825. @FileStoreHandler.deleteFile.calledWith(project_id, @entity_id).should.equal false
  826. it "should not attempt to delete from the document updater", ->
  827. @DocumentUpdaterHandler.deleteDoc.called.should.equal false
  828. it "should should send the update to the doc updater", ->
  829. oldFiles = [ file: @entity, path: @path ]
  830. @DocumentUpdaterHandler.updateProjectStructure
  831. .calledWith(project_id, projectHistoryId, userId, {oldFiles, newProject: @newProject})
  832. .should.equal true
  833. describe "a doc", ->
  834. beforeEach (done) ->
  835. @path = "/file/system/path.tex"
  836. @ProjectEntityUpdateHandler._cleanUpDoc = sinon.stub().yields()
  837. @entity = {_id: @entity_id}
  838. @newProject = "new-project"
  839. @ProjectEntityUpdateHandler._cleanUpEntity @project, @newProject, @entity, 'doc', @path, userId, done
  840. it "should clean up the doc", ->
  841. @ProjectEntityUpdateHandler._cleanUpDoc
  842. .calledWith(@project, @entity, @path, userId)
  843. .should.equal true
  844. it "should should send the update to the doc updater", ->
  845. oldDocs = [ doc: @entity, path: @path ]
  846. @DocumentUpdaterHandler.updateProjectStructure
  847. .calledWith(project_id, projectHistoryId, userId, {oldDocs, newProject: @newProject})
  848. .should.equal true
  849. describe "a folder", ->
  850. beforeEach (done) ->
  851. @folder =
  852. folders: [
  853. name: "subfolder"
  854. fileRefs: [ @file1 = { _id: "file-id-1", name: "file-name-1"} ]
  855. docs: [ @doc1 = { _id: "doc-id-1", name: "doc-name-1" } ]
  856. folders: []
  857. ]
  858. fileRefs: [ @file2 = { _id: "file-id-2", name: "file-name-2" } ]
  859. docs: [ @doc2 = { _id: "doc-id-2", name: "doc-name-2" } ]
  860. @ProjectEntityUpdateHandler._cleanUpDoc = sinon.stub().yields()
  861. @ProjectEntityUpdateHandler._cleanUpFile = sinon.stub().yields()
  862. path = "/folder"
  863. @newProject = "new-project"
  864. @ProjectEntityUpdateHandler._cleanUpEntity @project, @newProject, @folder, "folder", path, userId, done
  865. it "should clean up all sub files", ->
  866. @ProjectEntityUpdateHandler._cleanUpFile
  867. .calledWith(@project, @file1, "/folder/subfolder/file-name-1", userId)
  868. .should.equal true
  869. @ProjectEntityUpdateHandler._cleanUpFile
  870. .calledWith(@project, @file2, "/folder/file-name-2", userId)
  871. .should.equal true
  872. it "should clean up all sub docs", ->
  873. @ProjectEntityUpdateHandler._cleanUpDoc
  874. .calledWith(@project, @doc1, "/folder/subfolder/doc-name-1", userId)
  875. .should.equal true
  876. @ProjectEntityUpdateHandler._cleanUpDoc
  877. .calledWith(@project, @doc2, "/folder/doc-name-2", userId)
  878. .should.equal true
  879. it "should should send one update to the doc updater for all docs and files", ->
  880. oldFiles = [ {file: @file2, path: "/folder/file-name-2"}, {file: @file1, path: "/folder/subfolder/file-name-1"} ]
  881. oldDocs = [ {doc: @doc2, path: "/folder/doc-name-2"}, { doc: @doc1, path: "/folder/subfolder/doc-name-1"} ]
  882. @DocumentUpdaterHandler.updateProjectStructure
  883. .calledWith(project_id, projectHistoryId, userId, {oldFiles, oldDocs, newProject: @newProject})
  884. .should.equal true
  885. describe "_cleanUpDoc", ->
  886. beforeEach ->
  887. @doc =
  888. _id: ObjectId()
  889. name: "test.tex"
  890. @path = "/path/to/doc"
  891. @ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
  892. @ProjectEntityMongoUpdateHandler._insertDeletedDocReference = sinon.stub().yields()
  893. @DocumentUpdaterHandler.deleteDoc = sinon.stub().yields()
  894. @DocstoreManager.deleteDoc = sinon.stub().yields()
  895. @callback = sinon.stub()
  896. describe "when the doc is the root doc", ->
  897. beforeEach ->
  898. @project.rootDoc_id = @doc._id
  899. @ProjectEntityUpdateHandler._cleanUpDoc @project, @doc, @path, userId, @callback
  900. it "should unset the root doc", ->
  901. @ProjectEntityUpdateHandler.unsetRootDoc
  902. .calledWith(project_id)
  903. .should.equal true
  904. it "should delete the doc in the doc updater", ->
  905. @DocumentUpdaterHandler.deleteDoc
  906. .calledWith(project_id, @doc._id.toString())
  907. it "should insert the doc into the deletedDocs array", ->
  908. @ProjectEntityMongoUpdateHandler._insertDeletedDocReference
  909. .calledWith(@project._id, @doc)
  910. .should.equal true
  911. it "should delete the doc in the doc store", ->
  912. @DocstoreManager.deleteDoc
  913. .calledWith(project_id, @doc._id.toString())
  914. .should.equal true
  915. it "should call the callback", ->
  916. @callback.called.should.equal true
  917. describe "when the doc is not the root doc", ->
  918. beforeEach ->
  919. @project.rootDoc_id = ObjectId()
  920. @ProjectEntityUpdateHandler._cleanUpDoc @project, @doc, @path, userId, @callback
  921. it "should not unset the root doc", ->
  922. @ProjectEntityUpdateHandler.unsetRootDoc.called.should.equal false
  923. it "should call the callback", ->
  924. @callback.called.should.equal true