ProjectEntityUpdateHandlerTests.coffee 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  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. doc_id = '4eecb1c1bffa66588e0000a2'
  13. file_id = "4eecaffcbffa66588e000009"
  14. folder_id = "4eecaffcbffa66588e000008"
  15. rootFolderId = "4eecaffcbffa66588e000007"
  16. userId = 1234
  17. beforeEach ->
  18. @project = _id: project_id, name: 'project name'
  19. @fileUrl = 'filestore.example.com/file'
  20. @FileStoreHandler =
  21. uploadFileFromDisk: sinon.stub().yields(null, @fileUrl)
  22. copyFile: sinon.stub().yields(null, @fileUrl)
  23. @DocModel = class Doc
  24. constructor:(options)->
  25. {@name, @lines} = options
  26. @_id = doc_id
  27. @rev = 0
  28. @FileModel = class File
  29. constructor:(options)->
  30. {@name} = options
  31. @._id = file_id
  32. @rev = 0
  33. @docName = "doc-name"
  34. @docLines = ['1234','abc']
  35. @fileName = "something.jpg"
  36. @fileSystemPath = "somehintg"
  37. @source = 'editor'
  38. @callback = sinon.stub()
  39. @ProjectEntityUpdateHandler = SandboxedModule.require modulePath, requires:
  40. 'logger-sharelatex': @logger = {log:sinon.stub(), error: sinon.stub(), err:->}
  41. '../../models/Doc': Doc:@DocModel
  42. '../Docstore/DocstoreManager': @DocstoreManager = {}
  43. '../Errors/Errors': Errors
  44. '../../Features/DocumentUpdater/DocumentUpdaterHandler':@DocumentUpdaterHandler =
  45. updateProjectStructure: sinon.stub().yields()
  46. '../../models/File': File:@FileModel
  47. '../FileStore/FileStoreHandler':@FileStoreHandler
  48. "../../infrastructure/LockManager":@LockManager =
  49. runWithLock:
  50. sinon.spy((namespace, id, runner, callback) -> runner(callback))
  51. '../../models/Project': Project:@ProjectModel = {}
  52. "./ProjectGetter": @ProjectGetter = {}
  53. './ProjectLocator': @ProjectLocator = {}
  54. './ProjectUpdateHandler': @ProjectUpdater = {}
  55. './ProjectEntityHandler': @ProjectEntityHandler = {}
  56. './ProjectEntityMongoUpdateHandler': @ProjectEntityMongoUpdateHandler = {}
  57. '../ThirdPartyDataStore/TpdsUpdateSender':@TpdsUpdateSender =
  58. addFile: sinon.stub().yields()
  59. describe 'copyFileFromExistingProjectWithProject', ->
  60. beforeEach ->
  61. @oldProject_id = "123kljadas"
  62. @oldFileRef = {name:@fileName, _id:"oldFileRef"}
  63. @ProjectEntityMongoUpdateHandler._confirmFolder = sinon.stub().yields(folder_id)
  64. @ProjectEntityMongoUpdateHandler._putElement = sinon.stub().yields(null, {path:{fileSystem: @fileSystemPath}})
  65. @ProjectEntityUpdateHandler.copyFileFromExistingProjectWithProject @project, folder_id, @oldProject_id, @oldFileRef, userId, @callback
  66. it 'should copy the file in FileStoreHandler', ->
  67. @FileStoreHandler.copyFile
  68. .calledWith(@oldProject_id, @oldFileRef._id, project_id, file_id)
  69. .should.equal true
  70. it 'should put file into folder by calling put element', ->
  71. @ProjectEntityMongoUpdateHandler._putElement
  72. .calledWithMatch(@project, folder_id, { _id: file_id, name: @fileName }, "file")
  73. .should.equal true
  74. it 'should return doc and parent folder', ->
  75. @callback.calledWithMatch(null,{ _id: file_id, name: @fileName }, folder_id).should.equal true
  76. it 'should call third party data store if versioning is enabled', ->
  77. @TpdsUpdateSender.addFile.calledWith(
  78. project_id: project_id
  79. file_id: file_id
  80. path: @fileSystemPath
  81. rev: 0
  82. project_name: @project.name
  83. ).should.equal true
  84. it "should should send the change in project structure to the doc updater", ->
  85. changesMatcher = sinon.match (changes) =>
  86. { newFiles } = changes
  87. return false if newFiles.length != 1
  88. newFile = newFiles[0]
  89. newFile.file._id == file_id &&
  90. newFile.path == @fileSystemPath &&
  91. newFile.url == @fileUrl
  92. @DocumentUpdaterHandler.updateProjectStructure
  93. .calledWithMatch(project_id, userId, changesMatcher)
  94. .should.equal true
  95. describe 'updateDocLines', ->
  96. beforeEach ->
  97. @path = "/somewhere/something.tex"
  98. @doc = {
  99. _id: doc_id
  100. }
  101. @version = 42
  102. @ranges = {"mock":"ranges"}
  103. @ProjectGetter.getProjectWithoutDocLines = sinon.stub().yields(null, @project)
  104. @ProjectLocator.findElement = sinon.stub().yields(null, @doc, {fileSystem: @path})
  105. @TpdsUpdateSender.addDoc = sinon.stub().yields()
  106. @ProjectUpdater.markAsUpdated = sinon.stub()
  107. @callback = sinon.stub()
  108. describe "when the doc has been modified", ->
  109. beforeEach ->
  110. @DocstoreManager.updateDoc = sinon.stub().yields(null, true, @rev = 5)
  111. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback
  112. it "should get the project without doc lines", ->
  113. @ProjectGetter.getProjectWithoutDocLines
  114. .calledWith(project_id)
  115. .should.equal true
  116. it "should find the doc", ->
  117. @ProjectLocator.findElement
  118. .calledWith({
  119. project: @project
  120. type: "docs"
  121. element_id: doc_id
  122. })
  123. .should.equal true
  124. it "should update the doc in the docstore", ->
  125. @DocstoreManager.updateDoc
  126. .calledWith(project_id, doc_id, @docLines, @version, @ranges)
  127. .should.equal true
  128. it "should mark the project as updated", ->
  129. @ProjectUpdater.markAsUpdated
  130. .calledWith(project_id)
  131. .should.equal true
  132. it "should send the doc the to the TPDS", ->
  133. @TpdsUpdateSender.addDoc
  134. .calledWith({
  135. project_id: project_id
  136. project_name: @project.name
  137. doc_id: doc_id
  138. rev: @rev
  139. path: @path
  140. })
  141. .should.equal true
  142. it "should call the callback", ->
  143. @callback.called.should.equal true
  144. describe "when the doc has not been modified", ->
  145. beforeEach ->
  146. @DocstoreManager.updateDoc = sinon.stub().yields(null, false, @rev = 5)
  147. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback
  148. it "should not mark the project as updated", ->
  149. @ProjectUpdater.markAsUpdated.called.should.equal false
  150. it "should not send the doc the to the TPDS", ->
  151. @TpdsUpdateSender.addDoc.called.should.equal false
  152. it "should call the callback", ->
  153. @callback.called.should.equal true
  154. describe "when the doc has been deleted", ->
  155. beforeEach ->
  156. @project.deletedDocs = [ _id: doc_id ]
  157. @ProjectGetter.getProjectWithoutDocLines = sinon.stub().yields(null, @project)
  158. @ProjectLocator.findElement = sinon.stub().yields(new Errors.NotFoundError)
  159. @DocstoreManager.updateDoc = sinon.stub().yields()
  160. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback
  161. it "should update the doc in the docstore", ->
  162. @DocstoreManager.updateDoc
  163. .calledWith(project_id, doc_id, @docLines, @version, @ranges)
  164. .should.equal true
  165. it "should not mark the project as updated", ->
  166. @ProjectUpdater.markAsUpdated.called.should.equal false
  167. it "should not send the doc the to the TPDS", ->
  168. @TpdsUpdateSender.addDoc.called.should.equal false
  169. it "should call the callback", ->
  170. @callback.called.should.equal true
  171. describe "when the doc is not related to the project", ->
  172. beforeEach ->
  173. @ProjectLocator.findElement = sinon.stub().yields()
  174. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback
  175. it "should log out the error", ->
  176. @logger.error
  177. .calledWith(
  178. project_id: project_id
  179. doc_id: doc_id
  180. lines: @docLines
  181. "doc not found while updating doc lines"
  182. )
  183. .should.equal true
  184. it "should return a not found error", ->
  185. @callback.calledWith(new Errors.NotFoundError()).should.equal true
  186. describe "when the project is not found", ->
  187. beforeEach ->
  188. @ProjectGetter.getProjectWithoutDocLines = sinon.stub().yields()
  189. @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback
  190. it "should return a not found error", ->
  191. @callback.calledWith(new Errors.NotFoundError()).should.equal true
  192. describe "setRootDoc", ->
  193. it "should call Project.update", ->
  194. rootDoc_id = "root-doc-id-123123"
  195. @ProjectModel.update = sinon.stub()
  196. @ProjectEntityUpdateHandler.setRootDoc project_id, rootDoc_id
  197. @ProjectModel.update
  198. .calledWith({_id : project_id}, {rootDoc_id})
  199. .should.equal true
  200. describe "unsetRootDoc", ->
  201. it "should call Project.update", ->
  202. @ProjectModel.update = sinon.stub()
  203. @ProjectEntityUpdateHandler.unsetRootDoc project_id
  204. @ProjectModel.update
  205. .calledWith({_id : project_id}, {$unset : {rootDoc_id: true}})
  206. .should.equal true
  207. describe "restoreDoc", ->
  208. beforeEach ->
  209. @doc = { "mock": "doc" }
  210. @ProjectEntityHandler.getDoc = sinon.stub().yields(null, @docLines)
  211. @ProjectEntityUpdateHandler.addDoc = sinon.stub().yields(null, @doc, folder_id)
  212. @ProjectEntityUpdateHandler.restoreDoc project_id, doc_id, @docName, @callback
  213. it 'should get the doc lines', ->
  214. @ProjectEntityHandler.getDoc
  215. .calledWith(project_id, doc_id, include_deleted: true)
  216. .should.equal true
  217. it "should add a new doc with these doc lines", ->
  218. @ProjectEntityUpdateHandler.addDoc
  219. .calledWith(project_id, null, @docName, @docLines)
  220. .should.equal true
  221. it "should call the callback with the new folder and doc", ->
  222. @callback.calledWith(null, @doc, folder_id).should.equal true
  223. describe 'addDoc', ->
  224. beforeEach ->
  225. @path = "/path/to/doc"
  226. @newDoc = _id: doc_id
  227. @ProjectEntityUpdateHandler.addDocWithoutUpdatingHistory =
  228. withoutLock: sinon.stub().yields(null, @newDoc, folder_id, @path)
  229. @ProjectEntityUpdateHandler.addDoc project_id, folder_id, @docName, @docLines, userId, @callback
  230. it "creates the doc without history", () ->
  231. @ProjectEntityUpdateHandler.addDocWithoutUpdatingHistory.withoutLock
  232. .calledWith(project_id, folder_id, @docName, @docLines, userId)
  233. .should.equal true
  234. it "sends the change in project structure to the doc updater", () ->
  235. newDocs = [
  236. doc: @newDoc
  237. path: @path
  238. docLines: @docLines.join('\n')
  239. ]
  240. @DocumentUpdaterHandler.updateProjectStructure
  241. .calledWith(project_id, userId, {newDocs})
  242. .should.equal true
  243. describe 'addFile', ->
  244. beforeEach ->
  245. @path = "/path/to/file"
  246. @newFile = _id: file_id
  247. @ProjectEntityUpdateHandler.addFileWithoutUpdatingHistory =
  248. withoutLock: sinon.stub().yields(null, @newFile, folder_id, @path, @fileUrl)
  249. @ProjectEntityUpdateHandler.addFile project_id, folder_id, @docName, @fileSystemPath, userId, @callback
  250. it "creates the doc without history", () ->
  251. @ProjectEntityUpdateHandler.addFileWithoutUpdatingHistory.withoutLock
  252. .calledWith(project_id, folder_id, @docName, @fileSystemPath, userId)
  253. .should.equal true
  254. it "sends the change in project structure to the doc updater", () ->
  255. newFiles = [
  256. file: @newFile
  257. path: @path
  258. url: @fileUrl
  259. ]
  260. @DocumentUpdaterHandler.updateProjectStructure
  261. .calledWith(project_id, userId, {newFiles})
  262. .should.equal true
  263. describe 'replaceFile', ->
  264. beforeEach ->
  265. @newFile = _id: file_id, rev: 0
  266. @path = "/path/to/file"
  267. @project = _id: project_id, name: 'some project'
  268. @ProjectEntityMongoUpdateHandler.replaceFile = sinon.stub().yields(null, @newFile, @project, fileSystem: @path)
  269. @ProjectEntityUpdateHandler.replaceFile project_id, file_id, @fileSystemPath, userId, @callback
  270. it 'uploads a new version of the file', ->
  271. @FileStoreHandler.uploadFileFromDisk
  272. .calledWith(project_id, file_id, @fileSystemPath)
  273. .should.equal true
  274. it 'replaces the file in mongo', ->
  275. @ProjectEntityMongoUpdateHandler.replaceFile
  276. .calledWith(project_id, file_id)
  277. .should.equal true
  278. it 'notifies the tpds', ->
  279. @TpdsUpdateSender.addFile
  280. .calledWith({
  281. project_id: project_id
  282. project_name: @project.name
  283. file_id: file_id
  284. rev: @newFile.rev + 1
  285. path: @path
  286. })
  287. .should.equal true
  288. it 'updates the project structure in the doc updater', ->
  289. newFiles = [
  290. file: @newFile
  291. path: @path
  292. url: @fileUrl
  293. ]
  294. @DocumentUpdaterHandler.updateProjectStructure
  295. .calledWith(project_id, userId, {newFiles})
  296. .should.equal true
  297. describe 'addDocWithoutUpdatingHistory', ->
  298. beforeEach ->
  299. @path = "/path/to/doc"
  300. @project = _id: project_id, name: 'some project'
  301. @TpdsUpdateSender.addDoc = sinon.stub().yields()
  302. @DocstoreManager.updateDoc = sinon.stub().yields(null, false, @rev = 5)
  303. @ProjectEntityMongoUpdateHandler.addDoc = sinon.stub().yields(null, {path: fileSystem: @path}, @project)
  304. @ProjectEntityUpdateHandler.addDocWithoutUpdatingHistory project_id, folder_id, @docName, @docLines, userId, @callback
  305. it "updates the doc in the docstore", () ->
  306. @DocstoreManager.updateDoc
  307. .calledWith(project_id, doc_id, @docLines, 0, {})
  308. .should.equal true
  309. it "updates the doc in mongo", () ->
  310. docMatcher = sinon.match (doc) =>
  311. doc.name == @docName
  312. @ProjectEntityMongoUpdateHandler.addDoc
  313. .calledWithMatch(project_id, folder_id, docMatcher)
  314. .should.equal true
  315. it "notifies the tpds", () ->
  316. @TpdsUpdateSender.addDoc
  317. .calledWith({
  318. project_id: project_id
  319. project_name: @project.name
  320. doc_id: doc_id
  321. rev: 0
  322. path: @path
  323. })
  324. .should.equal true
  325. it "should not should send the change in project structure to the doc updater", () ->
  326. @DocumentUpdaterHandler.updateProjectStructure
  327. .called
  328. .should.equal false
  329. describe 'addFileWithoutUpdatingHistory', ->
  330. beforeEach ->
  331. @path = "/path/to/file"
  332. @project = _id: project_id, name: 'some project'
  333. @TpdsUpdateSender.addFile = sinon.stub().yields()
  334. @ProjectEntityMongoUpdateHandler.addFile = sinon.stub().yields(null, {path: fileSystem: @path}, @project)
  335. @ProjectEntityUpdateHandler.addFileWithoutUpdatingHistory project_id, folder_id, @fileName, @fileSystemPath, userId, @callback
  336. it "updates the file in the filestore", () ->
  337. @FileStoreHandler.uploadFileFromDisk
  338. .calledWith(project_id, file_id, @fileSystemPath)
  339. .should.equal true
  340. it "updates the file in mongo", () ->
  341. fileMatcher = sinon.match (file) =>
  342. file.name == @fileName
  343. @ProjectEntityMongoUpdateHandler.addFile
  344. .calledWithMatch(project_id, folder_id, fileMatcher)
  345. .should.equal true
  346. it "notifies the tpds", () ->
  347. @TpdsUpdateSender.addFile
  348. .calledWith({
  349. project_id: project_id
  350. project_name: @project.name
  351. file_id: file_id
  352. rev: 0
  353. path: @path
  354. })
  355. .should.equal true
  356. it "should not should send the change in project structure to the doc updater", () ->
  357. @DocumentUpdaterHandler.updateProjectStructure
  358. .called
  359. .should.equal false
  360. describe 'upsertDoc', ->
  361. describe 'upserting into an invalid folder', ->
  362. beforeEach ->
  363. @ProjectLocator.findElement = sinon.stub().yields()
  364. @ProjectEntityUpdateHandler.upsertDoc project_id, folder_id, @docName, @docLines, @source, userId, @callback
  365. it 'returns an error', ->
  366. errorMatcher = sinon.match.instanceOf(Error)
  367. @callback.calledWithMatch(errorMatcher)
  368. .should.equal true
  369. describe 'updating an existing doc', ->
  370. beforeEach ->
  371. @existingDoc = _id: doc_id, name: @docName
  372. @folder = _id: folder_id, docs: [@existingDoc]
  373. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  374. @DocumentUpdaterHandler.setDocument = sinon.stub().yields()
  375. @DocumentUpdaterHandler.flushDocToMongo = sinon.stub().yields()
  376. @ProjectEntityUpdateHandler.upsertDoc project_id, folder_id, @docName, @docLines, @source, userId, @callback
  377. it 'tries to find the folder', ->
  378. @ProjectLocator.findElement
  379. .calledWith({project_id, element_id: folder_id, type: "folder"})
  380. .should.equal true
  381. it 'updates the doc contents', ->
  382. @DocumentUpdaterHandler.setDocument
  383. .calledWith(project_id, @existingDoc._id, userId, @docLines, @source)
  384. .should.equal true
  385. it 'flushes the doc contents', ->
  386. @DocumentUpdaterHandler.flushDocToMongo
  387. .calledWith(project_id, @existingDoc._id )
  388. .should.equal true
  389. it 'returns the doc', ->
  390. @callback.calledWith(null, @existingDoc, false)
  391. describe 'creating a new doc', ->
  392. beforeEach ->
  393. @folder = _id: folder_id, docs: []
  394. @newDoc = _id: doc_id
  395. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  396. @ProjectEntityUpdateHandler.addDoc = withoutLock: sinon.stub().yields(null, @newDoc)
  397. @ProjectEntityUpdateHandler.upsertDoc project_id, folder_id, @docName, @docLines, @source, userId, @callback
  398. it 'tries to find the folder', ->
  399. @ProjectLocator.findElement
  400. .calledWith({project_id, element_id: folder_id, type: "folder"})
  401. .should.equal true
  402. it 'adds the doc', ->
  403. @ProjectEntityUpdateHandler.addDoc.withoutLock
  404. .calledWith(project_id, folder_id, @docName, @docLines, userId)
  405. .should.equal true
  406. it 'returns the doc', ->
  407. @callback.calledWith(null, @newDoc, true)
  408. describe 'upsertFile', ->
  409. describe 'upserting into an invalid folder', ->
  410. beforeEach ->
  411. @ProjectLocator.findElement = sinon.stub().yields()
  412. @ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, userId, @callback
  413. it 'returns an error', ->
  414. errorMatcher = sinon.match.instanceOf(Error)
  415. @callback.calledWithMatch(errorMatcher)
  416. .should.equal true
  417. describe 'updating an existing file', ->
  418. beforeEach ->
  419. @existingFile = _id: file_id, name: @fileName
  420. @folder = _id: folder_id, fileRefs: [@existingFile]
  421. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  422. @ProjectEntityUpdateHandler.replaceFile = withoutLock: sinon.stub().yields(null, @newFile)
  423. @ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, userId, @callback
  424. it 'replaces the file', ->
  425. @ProjectEntityUpdateHandler.replaceFile.withoutLock
  426. .calledWith(project_id, file_id, @fileSystemPath, userId)
  427. .should.equal true
  428. it 'returns the file', ->
  429. @callback.calledWith(null, @existingFile, false)
  430. describe 'creating a new file', ->
  431. beforeEach ->
  432. @folder = _id: folder_id, fileRefs: []
  433. @newFile = _id: file_id
  434. @ProjectLocator.findElement = sinon.stub().yields(null, @folder)
  435. @ProjectEntityUpdateHandler.addFile = withoutLock: sinon.stub().yields(null, @newFile)
  436. @ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, userId, @callback
  437. it 'tries to find the folder', ->
  438. @ProjectLocator.findElement
  439. .calledWith({project_id, element_id: folder_id, type: "folder"})
  440. .should.equal true
  441. it 'adds the file', ->
  442. @ProjectEntityUpdateHandler.addFile.withoutLock
  443. .calledWith(project_id, folder_id, @fileName, @fileSystemPath, userId)
  444. .should.equal true
  445. it 'returns the file', ->
  446. @callback.calledWith(null, @newFile, true)
  447. describe 'upsertDocWithPath', ->
  448. beforeEach ->
  449. @path = "/folder/doc.tex"
  450. @newFolders = [ 'mock-a', 'mock-b' ]
  451. @folder = _id: folder_id
  452. @doc = _id: doc_id
  453. @isNewDoc = true
  454. @ProjectEntityUpdateHandler.mkdirp =
  455. withoutLock: sinon.stub().yields(null, @newFolders, @folder)
  456. @ProjectEntityUpdateHandler.upsertDoc =
  457. withoutLock: sinon.stub().yields(null, @doc, @isNewDoc)
  458. @ProjectEntityUpdateHandler.upsertDocWithPath project_id, @path, @docLines, @source, userId, @callback
  459. it 'creates any necessary folders', ->
  460. @ProjectEntityUpdateHandler.mkdirp.withoutLock
  461. .calledWith(project_id, '/folder')
  462. .should.equal true
  463. it 'upserts the doc', ->
  464. @ProjectEntityUpdateHandler.upsertDoc.withoutLock
  465. .calledWith(project_id, @folder._id, 'doc.tex', @docLines, @source, userId)
  466. .should.equal true
  467. it 'calls the callback', ->
  468. @callback
  469. .calledWith(null, @doc, @isNewDoc, @newFolders, @folder)
  470. .should.equal true
  471. describe 'upsertFileWithPath', ->
  472. beforeEach ->
  473. @path = "/folder/file.png"
  474. @newFolders = [ 'mock-a', 'mock-b' ]
  475. @folder = _id: folder_id
  476. @file = _id: file_id
  477. @isNewFile = true
  478. @ProjectEntityUpdateHandler.mkdirp =
  479. withoutLock: sinon.stub().yields(null, @newFolders, @folder)
  480. @ProjectEntityUpdateHandler.upsertFile =
  481. withoutLock: sinon.stub().yields(null, @file, @isNewFile)
  482. @ProjectEntityUpdateHandler.upsertFileWithPath project_id, @path, @fileSystemPath, userId, @callback
  483. it 'creates any necessary folders', ->
  484. @ProjectEntityUpdateHandler.mkdirp.withoutLock
  485. .calledWith(project_id, '/folder')
  486. .should.equal true
  487. it 'upserts the file', ->
  488. @ProjectEntityUpdateHandler.upsertFile.withoutLock
  489. .calledWith(project_id, @folder._id, 'file.png', @fileSystemPath, userId)
  490. .should.equal true
  491. it 'calls the callback', ->
  492. @callback
  493. .calledWith(null, @file, @isNewFile, @newFolders, @folder)
  494. .should.equal true
  495. describe 'deleteEntity', ->
  496. beforeEach ->
  497. @path = '/path/to/doc.tex'
  498. @doc = _id: doc_id
  499. @projectBeforeDeletion = _id: project_id, name: 'project'
  500. @ProjectEntityMongoUpdateHandler.deleteEntity = sinon.stub().yields(null, @doc, {fileSystem: @path}, @projectBeforeDeletion)
  501. @ProjectEntityUpdateHandler._cleanUpEntity = sinon.stub().yields()
  502. @TpdsUpdateSender.deleteEntity = sinon.stub().yields()
  503. @ProjectEntityUpdateHandler.deleteEntity project_id, doc_id, 'doc', userId, @callback
  504. it 'deletes the entity in mongo', ->
  505. @ProjectEntityMongoUpdateHandler.deleteEntity
  506. .calledWith(project_id, doc_id, 'doc')
  507. .should.equal true
  508. it 'cleans up the doc in the docstore', ->
  509. @ProjectEntityUpdateHandler._cleanUpEntity
  510. .calledWith(@projectBeforeDeletion, @doc, 'doc', @path, userId)
  511. .should.equal true
  512. it 'it notifies the tpds', ->
  513. @TpdsUpdateSender.deleteEntity
  514. .calledWith({ project_id, @path, project_name: @projectBeforeDeletion.name })
  515. .should.equal true
  516. it 'retuns the entity_id', ->
  517. @callback.calledWith(null, doc_id).should.equal true
  518. describe 'deleteEntityWithPath', ->
  519. describe 'when the entity exists', ->
  520. beforeEach ->
  521. @doc = _id: doc_id
  522. @ProjectLocator.findElementByPath = sinon.stub().yields(null, @doc, 'doc')
  523. @ProjectEntityUpdateHandler.deleteEntity =
  524. withoutLock: sinon.stub().yields()
  525. @path = '/path/to/doc.tex'
  526. @ProjectEntityUpdateHandler.deleteEntityWithPath project_id, @path, userId, @callback
  527. it 'finds the entity', ->
  528. @ProjectLocator.findElementByPath
  529. .calledWith({project_id, @path})
  530. .should.equal true
  531. it 'deletes the entity', ->
  532. @ProjectEntityUpdateHandler.deleteEntity.withoutLock
  533. .calledWith(project_id, @doc._id, 'doc', userId, @callback)
  534. .should.equal true
  535. describe 'when the entity does not exist', ->
  536. beforeEach ->
  537. @ProjectLocator.findElementByPath = sinon.stub().yields()
  538. @path = '/doc.tex'
  539. @ProjectEntityUpdateHandler.deleteEntityWithPath project_id, @path, userId, @callback
  540. it 'returns an error', ->
  541. @callback.calledWith(new Errors.NotFoundError()).should.equal true
  542. describe 'mkdirp', ->
  543. beforeEach ->
  544. @docPath = '/folder/doc.tex'
  545. @ProjectEntityMongoUpdateHandler.mkdirp = sinon.stub().yields()
  546. @ProjectEntityUpdateHandler.mkdirp project_id, @docPath, @callback
  547. it 'calls ProjectEntityMongoUpdateHandler', ->
  548. @ProjectEntityMongoUpdateHandler.mkdirp
  549. .calledWith(project_id, @docPath)
  550. .should.equal true
  551. describe 'addFolder', ->
  552. beforeEach ->
  553. @parentFolder_id = '123asdf'
  554. @folderName = 'new-folder'
  555. @ProjectEntityMongoUpdateHandler.addFolder = sinon.stub().yields()
  556. @ProjectEntityUpdateHandler.addFolder project_id, @parentFolder_id, @folderName, @callback
  557. it 'calls ProjectEntityMongoUpdateHandler', ->
  558. @ProjectEntityMongoUpdateHandler.addFolder
  559. .calledWith(project_id, @parentFolder_id, @folderName)
  560. .should.equal true
  561. describe 'moveEntity', ->
  562. beforeEach ->
  563. @project_name = 'project name'
  564. @startPath = '/a.tex'
  565. @endPath = '/folder/b.tex'
  566. @rev = 2
  567. @changes = newDocs: ['old-doc'], newFiles: ['old-file']
  568. @ProjectEntityMongoUpdateHandler.moveEntity = sinon.stub().yields(
  569. null, @project_name, @startPath, @endPath, @rev, @changes
  570. )
  571. @TpdsUpdateSender.moveEntity = sinon.stub()
  572. @DocumentUpdaterHandler.updateProjectStructure = sinon.stub()
  573. @ProjectEntityUpdateHandler.moveEntity project_id, doc_id, folder_id, 'doc', userId, @callback
  574. it 'moves the entity in mongo', ->
  575. @ProjectEntityMongoUpdateHandler.moveEntity
  576. .calledWith(project_id, doc_id, folder_id, 'doc')
  577. .should.equal true
  578. it 'notifies tpds', ->
  579. @TpdsUpdateSender.moveEntity
  580. .calledWith({project_id, @project_name, @startPath, @endPath, @rev})
  581. .should.equal true
  582. it 'sends the changes in project structure to the doc updater', ->
  583. @DocumentUpdaterHandler.updateProjectStructure
  584. .calledWith(project_id, userId, @changes, @callback)
  585. .should.equal true
  586. describe "renameEntity", ->
  587. beforeEach ->
  588. @project_name = 'project name'
  589. @startPath = '/folder/a.tex'
  590. @endPath = '/folder/b.tex'
  591. @rev = 2
  592. @changes = newDocs: ['old-doc'], newFiles: ['old-file']
  593. @newDocName = 'b.tex'
  594. @ProjectEntityMongoUpdateHandler.renameEntity = sinon.stub().yields(
  595. null, @project_name, @startPath, @endPath, @rev, @changes
  596. )
  597. @TpdsUpdateSender.moveEntity = sinon.stub()
  598. @DocumentUpdaterHandler.updateProjectStructure = sinon.stub()
  599. @ProjectEntityUpdateHandler.renameEntity project_id, doc_id, 'doc', @newDocName, userId, @callback
  600. it 'moves the entity in mongo', ->
  601. @ProjectEntityMongoUpdateHandler.renameEntity
  602. .calledWith(project_id, doc_id, 'doc', @newDocName)
  603. .should.equal true
  604. it 'notifies tpds', ->
  605. @TpdsUpdateSender.moveEntity
  606. .calledWith({project_id, @project_name, @startPath, @endPath, @rev})
  607. .should.equal true
  608. it 'sends the changes in project structure to the doc updater', ->
  609. @DocumentUpdaterHandler.updateProjectStructure
  610. .calledWith(project_id, userId, @changes, @callback)
  611. .should.equal true
  612. describe "_cleanUpEntity", ->
  613. beforeEach ->
  614. @entity_id = "4eecaffcbffa66588e000009"
  615. @FileStoreHandler.deleteFile = sinon.stub().yields()
  616. @DocumentUpdaterHandler.deleteDoc = sinon.stub().yields()
  617. @ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
  618. describe "a file", ->
  619. beforeEach (done) ->
  620. @path = "/file/system/path.png"
  621. @entity = _id: @entity_id
  622. @ProjectEntityUpdateHandler._cleanUpEntity @project, @entity, 'file', @path, userId, done
  623. it "should delete the file from FileStoreHandler", ->
  624. @FileStoreHandler.deleteFile.calledWith(project_id, @entity_id).should.equal true
  625. it "should not attempt to delete from the document updater", ->
  626. @DocumentUpdaterHandler.deleteDoc.called.should.equal false
  627. it "should should send the update to the doc updater", ->
  628. oldFiles = [ file: @entity, path: @path ]
  629. @DocumentUpdaterHandler.updateProjectStructure
  630. .calledWith(project_id, userId, {oldFiles})
  631. .should.equal true
  632. describe "a doc", ->
  633. beforeEach (done) ->
  634. @path = "/file/system/path.tex"
  635. @ProjectEntityUpdateHandler._cleanUpDoc = sinon.stub().yields()
  636. @entity = {_id: @entity_id}
  637. @ProjectEntityUpdateHandler._cleanUpEntity @project, @entity, 'doc', @path, userId, done
  638. it "should clean up the doc", ->
  639. @ProjectEntityUpdateHandler._cleanUpDoc
  640. .calledWith(@project, @entity, @path, userId)
  641. .should.equal true
  642. describe "a folder", ->
  643. beforeEach (done) ->
  644. @folder =
  645. folders: [
  646. name: "subfolder"
  647. fileRefs: [ @file1 = { _id: "file-id-1", name: "file-name-1"} ]
  648. docs: [ @doc1 = { _id: "doc-id-1", name: "doc-name-1" } ]
  649. folders: []
  650. ]
  651. fileRefs: [ @file2 = { _id: "file-id-2", name: "file-name-2" } ]
  652. docs: [ @doc2 = { _id: "doc-id-2", name: "doc-name-2" } ]
  653. @ProjectEntityUpdateHandler._cleanUpDoc = sinon.stub().yields()
  654. @ProjectEntityUpdateHandler._cleanUpFile = sinon.stub().yields()
  655. path = "/folder"
  656. @ProjectEntityUpdateHandler._cleanUpEntity @project, @folder, "folder", path, userId, done
  657. it "should clean up all sub files", ->
  658. @ProjectEntityUpdateHandler._cleanUpFile
  659. .calledWith(@project, @file1, "/folder/subfolder/file-name-1", userId)
  660. .should.equal true
  661. @ProjectEntityUpdateHandler._cleanUpFile
  662. .calledWith(@project, @file2, "/folder/file-name-2", userId)
  663. .should.equal true
  664. it "should clean up all sub docs", ->
  665. @ProjectEntityUpdateHandler._cleanUpDoc
  666. .calledWith(@project, @doc1, "/folder/subfolder/doc-name-1", userId)
  667. .should.equal true
  668. @ProjectEntityUpdateHandler._cleanUpDoc
  669. .calledWith(@project, @doc2, "/folder/doc-name-2", userId)
  670. .should.equal true
  671. describe "_cleanUpDoc", ->
  672. beforeEach ->
  673. @project =
  674. _id: ObjectId(project_id)
  675. @doc =
  676. _id: ObjectId()
  677. name: "test.tex"
  678. @path = "/path/to/doc"
  679. @ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
  680. @ProjectEntityUpdateHandler._insertDeletedDocReference = sinon.stub().yields()
  681. @DocumentUpdaterHandler.deleteDoc = sinon.stub().yields()
  682. @DocstoreManager.deleteDoc = sinon.stub().yields()
  683. @callback = sinon.stub()
  684. describe "when the doc is the root doc", ->
  685. beforeEach ->
  686. @project.rootDoc_id = @doc._id
  687. @ProjectEntityUpdateHandler._cleanUpDoc @project, @doc, @path, userId, @callback
  688. it "should unset the root doc", ->
  689. @ProjectEntityUpdateHandler.unsetRootDoc
  690. .calledWith(project_id)
  691. .should.equal true
  692. it "should delete the doc in the doc updater", ->
  693. @DocumentUpdaterHandler.deleteDoc
  694. .calledWith(project_id, @doc._id.toString())
  695. it "should insert the doc into the deletedDocs array", ->
  696. @ProjectEntityUpdateHandler._insertDeletedDocReference
  697. .calledWith(@project._id, @doc)
  698. .should.equal true
  699. it "should delete the doc in the doc store", ->
  700. @DocstoreManager.deleteDoc
  701. .calledWith(project_id, @doc._id.toString())
  702. .should.equal true
  703. it "should should send the update to the doc updater", ->
  704. oldDocs = [ doc: @doc, path: @path ]
  705. @DocumentUpdaterHandler.updateProjectStructure
  706. .calledWith(project_id, userId, {oldDocs})
  707. .should.equal true
  708. it "should call the callback", ->
  709. @callback.called.should.equal true
  710. describe "when the doc is not the root doc", ->
  711. beforeEach ->
  712. @project.rootDoc_id = ObjectId()
  713. @ProjectEntityUpdateHandler._cleanUpDoc @project, @doc, @path, userId, @callback
  714. it "should not unset the root doc", ->
  715. @ProjectEntityUpdateHandler.unsetRootDoc.called.should.equal false
  716. it "should call the callback", ->
  717. @callback.called.should.equal true
  718. describe "_insertDeletedDocReference", ->
  719. beforeEach ->
  720. @doc =
  721. _id: ObjectId()
  722. name: "test.tex"
  723. @callback = sinon.stub()
  724. @ProjectModel.update = sinon.stub().yields()
  725. @ProjectEntityUpdateHandler._insertDeletedDocReference project_id, @doc, @callback
  726. it "should insert the doc into deletedDocs", ->
  727. @ProjectModel.update
  728. .calledWith({
  729. _id: project_id
  730. }, {
  731. $push: {
  732. deletedDocs: {
  733. _id: @doc._id
  734. name: @doc.name
  735. }
  736. }
  737. })
  738. .should.equal true
  739. it "should call the callback", ->
  740. @callback.called.should.equal true