ApplyingUpdatesToProjectStructureTests.coffee 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. sinon = require "sinon"
  2. chai = require("chai")
  3. chai.should()
  4. Settings = require('settings-sharelatex')
  5. rclient_history = require("redis-sharelatex").createClient(Settings.redis.history)
  6. ProjectHistoryKeys = Settings.redis.project_history.key_schema
  7. MockProjectHistoryApi = require "./helpers/MockProjectHistoryApi"
  8. MockWebApi = require "./helpers/MockWebApi"
  9. DocUpdaterClient = require "./helpers/DocUpdaterClient"
  10. DocUpdaterApp = require "./helpers/DocUpdaterApp"
  11. describe "Applying updates to a project's structure", ->
  12. before ->
  13. @user_id = 'user-id-123'
  14. @version = 1234
  15. describe "renaming a file", ->
  16. before (done) ->
  17. @project_id = DocUpdaterClient.randomId()
  18. @fileUpdate =
  19. id: DocUpdaterClient.randomId()
  20. pathname: '/file-path'
  21. newPathname: '/new-file-path'
  22. @fileUpdates = [ @fileUpdate ]
  23. DocUpdaterApp.ensureRunning (error) =>
  24. throw error if error?
  25. DocUpdaterClient.sendProjectUpdate @project_id, @user_id, [], @fileUpdates, @version, (error) ->
  26. throw error if error?
  27. setTimeout done, 200
  28. it "should push the applied file renames to the project history api", (done) ->
  29. rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
  30. throw error if error?
  31. update = JSON.parse(updates[0])
  32. update.file.should.equal @fileUpdate.id
  33. update.pathname.should.equal '/file-path'
  34. update.new_pathname.should.equal '/new-file-path'
  35. update.meta.user_id.should.equal @user_id
  36. update.meta.ts.should.be.a('string')
  37. update.version.should.equal "#{@version}.0"
  38. done()
  39. return null
  40. describe "renaming a document", ->
  41. before ->
  42. @docUpdate =
  43. id: DocUpdaterClient.randomId()
  44. pathname: '/doc-path'
  45. newPathname: '/new-doc-path'
  46. @docUpdates = [ @docUpdate ]
  47. describe "when the document is not loaded", ->
  48. before (done) ->
  49. @project_id = DocUpdaterClient.randomId()
  50. DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], @version, (error) ->
  51. throw error if error?
  52. setTimeout done, 200
  53. return null
  54. it "should push the applied doc renames to the project history api", (done) ->
  55. rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
  56. throw error if error?
  57. update = JSON.parse(updates[0])
  58. update.doc.should.equal @docUpdate.id
  59. update.pathname.should.equal '/doc-path'
  60. update.new_pathname.should.equal '/new-doc-path'
  61. update.meta.user_id.should.equal @user_id
  62. update.meta.ts.should.be.a('string')
  63. update.version.should.equal "#{@version}.0"
  64. done()
  65. return null
  66. describe "when the document is loaded", ->
  67. before (done) ->
  68. @project_id = DocUpdaterClient.randomId()
  69. MockWebApi.insertDoc @project_id, @docUpdate.id, {}
  70. DocUpdaterClient.preloadDoc @project_id, @docUpdate.id, (error) =>
  71. throw error if error?
  72. sinon.spy MockWebApi, "getDocument"
  73. DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], @version, (error) ->
  74. throw error if error?
  75. setTimeout done, 200
  76. return null
  77. after ->
  78. MockWebApi.getDocument.restore()
  79. it "should update the doc", (done) ->
  80. DocUpdaterClient.getDoc @project_id, @docUpdate.id, (error, res, doc) =>
  81. doc.pathname.should.equal @docUpdate.newPathname
  82. done()
  83. return null
  84. it "should push the applied doc renames to the project history api", (done) ->
  85. rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
  86. throw error if error?
  87. update = JSON.parse(updates[0])
  88. update.doc.should.equal @docUpdate.id
  89. update.pathname.should.equal '/doc-path'
  90. update.new_pathname.should.equal '/new-doc-path'
  91. update.meta.user_id.should.equal @user_id
  92. update.meta.ts.should.be.a('string')
  93. update.version.should.equal "#{@version}.0"
  94. done()
  95. return null
  96. describe "renaming multiple documents and files", ->
  97. before ->
  98. @docUpdate0 =
  99. id: DocUpdaterClient.randomId()
  100. pathname: '/doc-path0'
  101. newPathname: '/new-doc-path0'
  102. @docUpdate1 =
  103. id: DocUpdaterClient.randomId()
  104. pathname: '/doc-path1'
  105. newPathname: '/new-doc-path1'
  106. @docUpdates = [ @docUpdate0, @docUpdate1 ]
  107. @fileUpdate0 =
  108. id: DocUpdaterClient.randomId()
  109. pathname: '/file-path0'
  110. newPathname: '/new-file-path0'
  111. @fileUpdate1 =
  112. id: DocUpdaterClient.randomId()
  113. pathname: '/file-path1'
  114. newPathname: '/new-file-path1'
  115. @fileUpdates = [ @fileUpdate0, @fileUpdate1 ]
  116. describe "when the documents are not loaded", ->
  117. before (done) ->
  118. @project_id = DocUpdaterClient.randomId()
  119. DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, @fileUpdates, @version, (error) ->
  120. throw error if error?
  121. setTimeout done, 200
  122. return null
  123. it "should push the applied doc renames to the project history api", (done) ->
  124. rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
  125. throw error if error?
  126. update = JSON.parse(updates[0])
  127. update.doc.should.equal @docUpdate0.id
  128. update.pathname.should.equal '/doc-path0'
  129. update.new_pathname.should.equal '/new-doc-path0'
  130. update.meta.user_id.should.equal @user_id
  131. update.meta.ts.should.be.a('string')
  132. update.version.should.equal "#{@version}.0"
  133. update = JSON.parse(updates[1])
  134. update.doc.should.equal @docUpdate1.id
  135. update.pathname.should.equal '/doc-path1'
  136. update.new_pathname.should.equal '/new-doc-path1'
  137. update.meta.user_id.should.equal @user_id
  138. update.meta.ts.should.be.a('string')
  139. update.version.should.equal "#{@version}.1"
  140. update = JSON.parse(updates[2])
  141. update.file.should.equal @fileUpdate0.id
  142. update.pathname.should.equal '/file-path0'
  143. update.new_pathname.should.equal '/new-file-path0'
  144. update.meta.user_id.should.equal @user_id
  145. update.meta.ts.should.be.a('string')
  146. update.version.should.equal "#{@version}.2"
  147. update = JSON.parse(updates[3])
  148. update.file.should.equal @fileUpdate1.id
  149. update.pathname.should.equal '/file-path1'
  150. update.new_pathname.should.equal '/new-file-path1'
  151. update.meta.user_id.should.equal @user_id
  152. update.meta.ts.should.be.a('string')
  153. update.version.should.equal "#{@version}.3"
  154. done()
  155. return null
  156. describe "adding a file", ->
  157. before (done) ->
  158. @project_id = DocUpdaterClient.randomId()
  159. @fileUpdate =
  160. id: DocUpdaterClient.randomId()
  161. pathname: '/file-path'
  162. url: 'filestore.example.com'
  163. @fileUpdates = [ @fileUpdate ]
  164. DocUpdaterClient.sendProjectUpdate @project_id, @user_id, [], @fileUpdates, @version, (error) ->
  165. throw error if error?
  166. setTimeout done, 200
  167. return null
  168. it "should push the file addition to the project history api", (done) ->
  169. rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
  170. throw error if error?
  171. update = JSON.parse(updates[0])
  172. update.file.should.equal @fileUpdate.id
  173. update.pathname.should.equal '/file-path'
  174. update.url.should.equal 'filestore.example.com'
  175. update.meta.user_id.should.equal @user_id
  176. update.meta.ts.should.be.a('string')
  177. update.version.should.equal "#{@version}.0"
  178. done()
  179. return null
  180. describe "adding a doc", ->
  181. before (done) ->
  182. @project_id = DocUpdaterClient.randomId()
  183. @docUpdate =
  184. id: DocUpdaterClient.randomId()
  185. pathname: '/file-path'
  186. docLines: 'a\nb'
  187. @docUpdates = [ @docUpdate ]
  188. DocUpdaterClient.sendProjectUpdate @project_id, @user_id, @docUpdates, [], @version, (error) ->
  189. throw error if error?
  190. setTimeout done, 200
  191. return null
  192. it "should push the doc addition to the project history api", (done) ->
  193. rclient_history.lrange ProjectHistoryKeys.projectHistoryOps({@project_id}), 0, -1, (error, updates) =>
  194. throw error if error?
  195. update = JSON.parse(updates[0])
  196. update.doc.should.equal @docUpdate.id
  197. update.pathname.should.equal '/file-path'
  198. update.docLines.should.equal 'a\nb'
  199. update.meta.user_id.should.equal @user_id
  200. update.meta.ts.should.be.a('string')
  201. update.version.should.equal "#{@version}.0"
  202. done()
  203. return null
  204. describe "with enough updates to flush to the history service", ->
  205. before (done) ->
  206. @project_id = DocUpdaterClient.randomId()
  207. @user_id = DocUpdaterClient.randomId()
  208. @version0 = 12345
  209. @version1 = @version0 + 1
  210. updates = []
  211. for v in [0..599] # Should flush after 500 ops
  212. updates.push
  213. id: DocUpdaterClient.randomId(),
  214. pathname: '/file-' + v
  215. docLines: 'a\nb'
  216. sinon.spy MockProjectHistoryApi, "flushProject"
  217. # Send updates in chunks to causes multiple flushes
  218. projectId = @project_id
  219. userId = @project_id
  220. DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 250), [], @version0, (error) ->
  221. throw error if error?
  222. DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(250), [], @version1, (error) ->
  223. throw error if error?
  224. setTimeout done, 2000
  225. return null
  226. after ->
  227. MockProjectHistoryApi.flushProject.restore()
  228. it "should flush project history", ->
  229. MockProjectHistoryApi.flushProject.calledWith(@project_id).should.equal true
  230. describe "with too few updates to flush to the history service", ->
  231. before (done) ->
  232. @project_id = DocUpdaterClient.randomId()
  233. @user_id = DocUpdaterClient.randomId()
  234. @version0 = 12345
  235. @version1 = @version0 + 1
  236. updates = []
  237. for v in [0..42] # Should flush after 500 ops
  238. updates.push
  239. id: DocUpdaterClient.randomId(),
  240. pathname: '/file-' + v
  241. docLines: 'a\nb'
  242. sinon.spy MockProjectHistoryApi, "flushProject"
  243. # Send updates in chunks
  244. projectId = @project_id
  245. userId = @project_id
  246. DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 10), [], @version0, (error) ->
  247. throw error if error?
  248. DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(10), [], @version1, (error) ->
  249. throw error if error?
  250. setTimeout done, 2000
  251. return null
  252. after ->
  253. MockProjectHistoryApi.flushProject.restore()
  254. it "should not flush project history", ->
  255. MockProjectHistoryApi.flushProject.calledWith(@project_id).should.equal false