DocumentManagerTests.coffee 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. modulePath = "../../../../app/js/DocumentManager.js"
  5. SandboxedModule = require('sandboxed-module')
  6. Errors = require "../../../../app/js/Errors"
  7. describe "DocumentManager", ->
  8. beforeEach ->
  9. @DocumentManager = SandboxedModule.require modulePath, requires:
  10. "./RedisManager": @RedisManager = {}
  11. "./PersistenceManager": @PersistenceManager = {}
  12. "./HistoryManager": @HistoryManager = {}
  13. "logger-sharelatex": @logger = {log: sinon.stub()}
  14. "./DocOpsManager": @DocOpsManager = {}
  15. "./Metrics": @Metrics =
  16. Timer: class Timer
  17. done: sinon.stub()
  18. "./WebRedisManager": @WebRedisManager = {}
  19. "./DiffCodec": @DiffCodec = {}
  20. "./UpdateManager": @UpdateManager = {}
  21. "./RangesManager": @RangesManager = {}
  22. @project_id = "project-id-123"
  23. @doc_id = "doc-id-123"
  24. @callback = sinon.stub()
  25. @lines = ["one", "two", "three"]
  26. @version = 42
  27. @ranges = { comments: "mock", entries: "mock" }
  28. describe "flushAndDeleteDoc", ->
  29. describe "successfully", ->
  30. beforeEach ->
  31. @RedisManager.removeDocFromMemory = sinon.stub().callsArg(2)
  32. @DocumentManager.flushDocIfLoaded = sinon.stub().callsArgWith(2)
  33. @HistoryManager.flushDocChanges = sinon.stub().callsArg(2)
  34. @DocumentManager.flushAndDeleteDoc @project_id, @doc_id, @callback
  35. it "should flush the doc", ->
  36. @DocumentManager.flushDocIfLoaded
  37. .calledWith(@project_id, @doc_id)
  38. .should.equal true
  39. it "should remove the doc from redis", ->
  40. @RedisManager.removeDocFromMemory
  41. .calledWith(@project_id, @doc_id)
  42. .should.equal true
  43. it "should call the callback without error", ->
  44. @callback.calledWith(null).should.equal true
  45. it "should time the execution", ->
  46. @Metrics.Timer::done.called.should.equal true
  47. it "should flush to the history api", ->
  48. @HistoryManager.flushDocChanges
  49. .calledWith(@project_id, @doc_id)
  50. .should.equal true
  51. describe "flushDocIfLoaded", ->
  52. describe "when the doc is in Redis", ->
  53. beforeEach ->
  54. @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
  55. @PersistenceManager.setDoc = sinon.stub().yields()
  56. @DocumentManager.flushDocIfLoaded @project_id, @doc_id, @callback
  57. it "should get the doc from redis", ->
  58. @RedisManager.getDoc
  59. .calledWith(@project_id, @doc_id)
  60. .should.equal true
  61. it "should write the doc lines to the persistence layer", ->
  62. @PersistenceManager.setDoc
  63. .calledWith(@project_id, @doc_id, @lines, @version, @ranges)
  64. .should.equal true
  65. it "should call the callback without error", ->
  66. @callback.calledWith(null).should.equal true
  67. it "should time the execution", ->
  68. @Metrics.Timer::done.called.should.equal true
  69. describe "when the document is not in Redis", ->
  70. beforeEach ->
  71. @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null, null)
  72. @PersistenceManager.setDoc = sinon.stub().yields()
  73. @DocOpsManager.flushDocOpsToMongo = sinon.stub().callsArgWith(2)
  74. @DocumentManager.flushDocIfLoaded @project_id, @doc_id, @callback
  75. it "should get the doc from redis", ->
  76. @RedisManager.getDoc
  77. .calledWith(@project_id, @doc_id)
  78. .should.equal true
  79. it "should not write anything to the persistence layer", ->
  80. @PersistenceManager.setDoc.called.should.equal false
  81. @DocOpsManager.flushDocOpsToMongo.called.should.equal false
  82. it "should call the callback without error", ->
  83. @callback.calledWith(null).should.equal true
  84. it "should time the execution", ->
  85. @Metrics.Timer::done.called.should.equal true
  86. describe "getDocAndRecentOps", ->
  87. describe "with a previous version specified", ->
  88. beforeEach ->
  89. @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
  90. @RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
  91. @DocumentManager.getDocAndRecentOps @project_id, @doc_id, @fromVersion, @callback
  92. it "should get the doc", ->
  93. @DocumentManager.getDoc
  94. .calledWith(@project_id, @doc_id)
  95. .should.equal true
  96. it "should get the doc ops", ->
  97. @RedisManager.getPreviousDocOps
  98. .calledWith(@doc_id, @fromVersion, @version)
  99. .should.equal true
  100. it "should call the callback with the doc info", ->
  101. @callback.calledWith(null, @lines, @version, @ops, @ranges).should.equal true
  102. it "should time the execution", ->
  103. @Metrics.Timer::done.called.should.equal true
  104. describe "with no previous version specified", ->
  105. beforeEach ->
  106. @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
  107. @RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
  108. @DocumentManager.getDocAndRecentOps @project_id, @doc_id, -1, @callback
  109. it "should get the doc", ->
  110. @DocumentManager.getDoc
  111. .calledWith(@project_id, @doc_id)
  112. .should.equal true
  113. it "should not need to get the doc ops", ->
  114. @RedisManager.getPreviousDocOps.called.should.equal false
  115. it "should call the callback with the doc info", ->
  116. @callback.calledWith(null, @lines, @version, [], @ranges).should.equal true
  117. it "should time the execution", ->
  118. @Metrics.Timer::done.called.should.equal true
  119. describe "getDoc", ->
  120. describe "when the doc exists in Redis", ->
  121. beforeEach ->
  122. @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
  123. @DocumentManager.getDoc @project_id, @doc_id, @callback
  124. it "should get the doc from Redis", ->
  125. @RedisManager.getDoc
  126. .calledWith(@project_id, @doc_id)
  127. .should.equal true
  128. it "should call the callback with the doc info", ->
  129. @callback.calledWith(null, @lines, @version, @ranges, true).should.equal true
  130. it "should time the execution", ->
  131. @Metrics.Timer::done.called.should.equal true
  132. describe "when the doc does not exist in Redis", ->
  133. beforeEach ->
  134. @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null, null, null)
  135. @PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
  136. @RedisManager.putDocInMemory = sinon.stub().yields()
  137. @DocumentManager.getDoc @project_id, @doc_id, @callback
  138. it "should try to get the doc from Redis", ->
  139. @RedisManager.getDoc
  140. .calledWith(@project_id, @doc_id)
  141. .should.equal true
  142. it "should get the doc from the PersistenceManager", ->
  143. @PersistenceManager.getDoc
  144. .calledWith(@project_id, @doc_id)
  145. .should.equal true
  146. it "should set the doc in Redis", ->
  147. @RedisManager.putDocInMemory
  148. .calledWith(@project_id, @doc_id, @lines, @version, @ranges)
  149. .should.equal true
  150. it "should call the callback with the doc info", ->
  151. @callback.calledWith(null, @lines, @version, @ranges, false).should.equal true
  152. it "should time the execution", ->
  153. @Metrics.Timer::done.called.should.equal true
  154. describe "setDoc", ->
  155. describe "with plain tex lines", ->
  156. beforeEach ->
  157. @beforeLines = ["before", "lines"]
  158. @afterLines = ["after", "lines"]
  159. @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @beforeLines, @version, @ranges, true)
  160. @DiffCodec.diffAsShareJsOp = sinon.stub().callsArgWith(2, null, @ops)
  161. @UpdateManager.applyUpdate = sinon.stub().callsArgWith(3, null)
  162. @DocumentManager.flushDocIfLoaded = sinon.stub().callsArg(2)
  163. @DocumentManager.flushAndDeleteDoc = sinon.stub().callsArg(2)
  164. describe "when already loaded", ->
  165. beforeEach ->
  166. @DocumentManager.setDoc @project_id, @doc_id, @afterLines, @source, @user_id, @callback
  167. it "should get the current doc lines", ->
  168. @DocumentManager.getDoc
  169. .calledWith(@project_id, @doc_id)
  170. .should.equal true
  171. it "should return a diff of the old and new lines", ->
  172. @DiffCodec.diffAsShareJsOp
  173. .calledWith(@beforeLines, @afterLines)
  174. .should.equal true
  175. it "should apply the diff as a ShareJS op", ->
  176. @UpdateManager.applyUpdate
  177. .calledWith(
  178. @project_id,
  179. @doc_id,
  180. {
  181. doc: @doc_id,
  182. v: @version,
  183. op: @ops,
  184. meta: {
  185. type: "external"
  186. source: @source
  187. user_id: @user_id
  188. }
  189. }
  190. )
  191. .should.equal true
  192. it "should flush the doc to Mongo", ->
  193. @DocumentManager.flushDocIfLoaded
  194. .calledWith(@project_id, @doc_id)
  195. .should.equal true
  196. it "should call the callback", ->
  197. @callback.calledWith(null).should.equal true
  198. it "should time the execution", ->
  199. @Metrics.Timer::done.called.should.equal true
  200. describe "when not already loaded", ->
  201. beforeEach ->
  202. @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @beforeLines, @version, false)
  203. @DocumentManager.setDoc @project_id, @doc_id, @afterLines, @source, @user_id, @callback
  204. it "should flush and delete the doc from the doc updater", ->
  205. @DocumentManager.flushAndDeleteDoc
  206. .calledWith(@project_id, @doc_id)
  207. .should.equal true
  208. describe "without new lines", ->
  209. beforeEach ->
  210. @DocumentManager.setDoc @project_id, @doc_id, null, @source, @user_id, @callback
  211. it "should return the callback with an error", ->
  212. @callback.calledWith(new Error("No lines were passed to setDoc"))
  213. it "should not try to get the doc lines", ->
  214. @DocumentManager.getDoc.called.should.equal false
  215. describe "acceptChanges", ->
  216. beforeEach ->
  217. @change_id = "mock-change-id"
  218. @version = 34
  219. @lines = ["original", "lines"]
  220. @ranges = { entries: "mock", comments: "mock" }
  221. @updated_ranges = { entries: "updated", comments: "updated" }
  222. @DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @ranges)
  223. @RangesManager.acceptChange = sinon.stub().yields(null, @updated_ranges)
  224. @RedisManager.updateDocument = sinon.stub().yields()
  225. describe "successfully", ->
  226. beforeEach ->
  227. @DocumentManager.acceptChange @project_id, @doc_id, @change_id, @callback
  228. it "should get the document's current ranges", ->
  229. @DocumentManager.getDoc
  230. .calledWith(@project_id, @doc_id)
  231. .should.equal true
  232. it "should apply the accept change to the ranges", ->
  233. @RangesManager.acceptChange
  234. .calledWith(@change_id, @ranges)
  235. .should.equal true
  236. it "should save the updated ranges", ->
  237. @RedisManager.updateDocument
  238. .calledWith(@doc_id, @lines, @version, [], @updated_ranges)
  239. .should.equal true
  240. it "should call the callback", ->
  241. @callback.called.should.equal true
  242. describe "when the doc is not found", ->
  243. beforeEach ->
  244. @DocumentManager.getDoc = sinon.stub().yields(null, null, null, null)
  245. @DocumentManager.acceptChange @project_id, @doc_id, @change_id, @callback
  246. it "should not save anything", ->
  247. @RedisManager.updateDocument.called.should.equal false
  248. it "should call the callback with a not found error", ->
  249. error = new Errors.NotFoundError("document not found: #{@doc_id}")
  250. @callback.calledWith(error).should.equal true
  251. describe "deleteComment", ->
  252. beforeEach ->
  253. @comment_id = "mock-comment-id"
  254. @version = 34
  255. @lines = ["original", "lines"]
  256. @ranges = { comments: ["one", "two", "three"] }
  257. @updated_ranges = { comments: ["one", "three"] }
  258. @DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @ranges)
  259. @RangesManager.deleteComment = sinon.stub().yields(null, @updated_ranges)
  260. @RedisManager.updateDocument = sinon.stub().yields()
  261. describe "successfully", ->
  262. beforeEach ->
  263. @DocumentManager.deleteComment @project_id, @doc_id, @comment_id, @callback
  264. it "should get the document's current ranges", ->
  265. @DocumentManager.getDoc
  266. .calledWith(@project_id, @doc_id)
  267. .should.equal true
  268. it "should delete the comment from the ranges", ->
  269. @RangesManager.deleteComment
  270. .calledWith(@comment_id, @ranges)
  271. .should.equal true
  272. it "should save the updated ranges", ->
  273. @RedisManager.updateDocument
  274. .calledWith(@doc_id, @lines, @version, [], @updated_ranges)
  275. .should.equal true
  276. it "should call the callback", ->
  277. @callback.called.should.equal true
  278. describe "when the doc is not found", ->
  279. beforeEach ->
  280. @DocumentManager.getDoc = sinon.stub().yields(null, null, null, null)
  281. @DocumentManager.acceptChange @project_id, @doc_id, @comment_id, @callback
  282. it "should not save anything", ->
  283. @RedisManager.updateDocument.called.should.equal false
  284. it "should call the callback with a not found error", ->
  285. error = new Errors.NotFoundError("document not found: #{@doc_id}")
  286. @callback.calledWith(error).should.equal true