ProjectEntityUpdateHandlerTests.coffee 30 KB

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