MongoManagerTests.coffee 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. sinon = require('sinon')
  2. chai = require('chai')
  3. should = chai.should()
  4. expect = chai.expect
  5. modulePath = "../../../../app/js/MongoManager.js"
  6. packModulePath = "../../../../app/js/PackManager.js"
  7. SandboxedModule = require('sandboxed-module')
  8. {ObjectId} = require("mongojs")
  9. tk = require "timekeeper"
  10. describe "MongoManager", ->
  11. beforeEach ->
  12. tk.freeze(new Date())
  13. @MongoManager = SandboxedModule.require modulePath, requires:
  14. "./mongojs" : { db: @db = {}, ObjectId: ObjectId }
  15. "./PackManager" : SandboxedModule.require packModulePath, requires:
  16. "./LockManager" : {}
  17. "./mongojs": {db: bson: BSON = sinon.stub(), ObjectId}
  18. "logger-sharelatex": {}
  19. @callback = sinon.stub()
  20. @doc_id = ObjectId().toString()
  21. @project_id = ObjectId().toString()
  22. afterEach ->
  23. tk.reset()
  24. describe "getLastCompressedUpdate", ->
  25. beforeEach ->
  26. @update = "mock-update"
  27. @db.docHistory = {}
  28. @db.docHistory.find = sinon.stub().returns @db.docHistory
  29. @db.docHistory.sort = sinon.stub().returns @db.docHistory
  30. @db.docHistory.limit = sinon.stub().returns @db.docHistory
  31. @db.docHistory.toArray = sinon.stub().callsArgWith(0, null, [@update])
  32. @MongoManager.getLastCompressedUpdate @doc_id, @callback
  33. it "should find the updates for the doc", ->
  34. @db.docHistory.find
  35. .calledWith(doc_id: ObjectId(@doc_id))
  36. .should.equal true
  37. it "should limit to one result", ->
  38. @db.docHistory.limit
  39. .calledWith(1)
  40. .should.equal true
  41. it "should sort in descending version order", ->
  42. @db.docHistory.sort
  43. .calledWith(v: -1)
  44. .should.equal true
  45. it "should call the call back with the update", ->
  46. @callback.calledWith(null, @update).should.equal true
  47. describe "deleteCompressedUpdate", ->
  48. beforeEach ->
  49. @update_id = ObjectId().toString()
  50. @db.docHistory =
  51. remove: sinon.stub().callsArg(1)
  52. @MongoManager.deleteCompressedUpdate(@update_id, @callback)
  53. it "should remove the update", ->
  54. @db.docHistory.remove
  55. .calledWith(_id: ObjectId(@update_id))
  56. .should.equal true
  57. it "should call the callback", ->
  58. @callback.called.should.equal true
  59. describe "popLastCompressedUpdate", ->
  60. describe "when there is no last update", ->
  61. beforeEach ->
  62. @MongoManager.getLastCompressedUpdate = sinon.stub().callsArgWith(1, null, null)
  63. @MongoManager.deleteCompressedUpdate = sinon.stub()
  64. @MongoManager.popLastCompressedUpdate @doc_id, @callback
  65. it "should get the last update", ->
  66. @MongoManager.getLastCompressedUpdate
  67. .calledWith(@doc_id)
  68. .should.equal true
  69. it "should not try to delete the last update", ->
  70. @MongoManager.deleteCompressedUpdate.called.should.equal false
  71. it "should call the callback with no update", ->
  72. @callback.calledWith(null, null).should.equal true
  73. describe "when there is an update", ->
  74. beforeEach ->
  75. @update = { _id: Object() }
  76. @MongoManager.getLastCompressedUpdate = sinon.stub().callsArgWith(1, null, @update)
  77. @MongoManager.deleteCompressedUpdate = sinon.stub().callsArgWith(1, null)
  78. @MongoManager.popLastCompressedUpdate @doc_id, @callback
  79. it "should get the last update", ->
  80. @MongoManager.getLastCompressedUpdate
  81. .calledWith(@doc_id)
  82. .should.equal true
  83. it "should delete the last update", ->
  84. @MongoManager.deleteCompressedUpdate
  85. .calledWith(@update._id)
  86. .should.equal true
  87. it "should call the callback with the update", ->
  88. @callback.calledWith(null, @update).should.equal true
  89. describe "when there is a last update in S3", ->
  90. beforeEach ->
  91. @update = { _id: Object(), v: 12345, inS3: true }
  92. @MongoManager.getLastCompressedUpdate = sinon.stub().callsArgWith(1, null, @update)
  93. @MongoManager.deleteCompressedUpdate = sinon.stub()
  94. @MongoManager.popLastCompressedUpdate @doc_id, @callback
  95. it "should get the last update", ->
  96. @MongoManager.getLastCompressedUpdate
  97. .calledWith(@doc_id)
  98. .should.equal true
  99. it "should not try to delete the last update", ->
  100. @MongoManager.deleteCompressedUpdate.called.should.equal false
  101. it "should call the callback with a null update and the correct version", ->
  102. @callback.calledWith(null, null, @update.v).should.equal true
  103. describe "insertCompressedUpdate", ->
  104. beforeEach ->
  105. @update = { op: "op", meta: "meta", v: "v"}
  106. @db.docHistory =
  107. insert: sinon.stub().callsArg(1)
  108. describe "temporarly", ->
  109. beforeEach ->
  110. @MongoManager.insertCompressedUpdate @project_id, @doc_id, @update, true, @callback
  111. it "should insert the update with a expiresAt field one week away", ->
  112. @db.docHistory.insert
  113. .calledWith({
  114. project_id: ObjectId(@project_id),
  115. doc_id: ObjectId(@doc_id),
  116. op: @update.op,
  117. meta: @update.meta,
  118. v: @update.v
  119. expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
  120. })
  121. .should.equal true
  122. it "should call the callback", ->
  123. @callback.called.should.equal true
  124. describe "permanenty", ->
  125. beforeEach ->
  126. @MongoManager.insertCompressedUpdate @project_id, @doc_id, @update, false, @callback
  127. it "should insert the update with no expiresAt field", ->
  128. @db.docHistory.insert
  129. .calledWith({
  130. project_id: ObjectId(@project_id),
  131. doc_id: ObjectId(@doc_id),
  132. op: @update.op,
  133. meta: @update.meta,
  134. v: @update.v
  135. })
  136. .should.equal true
  137. it "should call the callback", ->
  138. @callback.called.should.equal true
  139. describe "insertCompressedUpdates", ->
  140. beforeEach (done) ->
  141. @updates = [ "mock-update-1", "mock-update-2" ]
  142. @MongoManager.insertCompressedUpdate = sinon.stub().callsArg(4)
  143. @MongoManager.insertCompressedUpdates @project_id, @doc_id, @updates, @temporary = true, (args...) =>
  144. @callback(args...)
  145. done()
  146. it "should insert each update", ->
  147. for update in @updates
  148. @MongoManager.insertCompressedUpdate
  149. .calledWith(@project_id, @doc_id, update, @temporary)
  150. .should.equal true
  151. it "should call the callback", ->
  152. @callback.called.should.equal true
  153. describe "getDocUpdates", ->
  154. beforeEach ->
  155. @results = [
  156. {foo: "mock-update", v: 56, doc_id: 100, project_id: 1},
  157. {foo: "mock-update", v: 55, doc_id: 100, project_id: 1},
  158. {pack: [ {foo: "mock-update", v: 54, doc_id: 100, project_id: 1},
  159. {foo: "mock-update", v: 53, doc_id: 100, project_id: 1},
  160. {foo: "mock-update", v: 52, doc_id: 100, project_id: 1} ]
  161. , v: 52, doc_id: 100, project_id: 1},
  162. {foo: "mock-update", v: 42, doc_id: 100, project_id: 1},
  163. {foo: "mock-update", v: 41, doc_id: 100, project_id: 1}
  164. ]
  165. @updates_between = [
  166. {foo: "mock-update", v: 55, doc_id: 100, project_id: 1},
  167. {foo: "mock-update", v: 54, doc_id: 100, project_id: 1},
  168. {foo: "mock-update", v: 53, doc_id: 100, project_id: 1},
  169. {foo: "mock-update", v: 52, doc_id: 100, project_id: 1},
  170. {foo: "mock-update", v: 42, doc_id: 100, project_id: 1}
  171. ]
  172. @updates_after = [
  173. {foo: "mock-update", v: 56, doc_id: 100, project_id: 1},
  174. {foo: "mock-update", v: 55, doc_id: 100, project_id: 1},
  175. {foo: "mock-update", v: 54, doc_id: 100, project_id: 1},
  176. {foo: "mock-update", v: 53, doc_id: 100, project_id: 1},
  177. {foo: "mock-update", v: 52, doc_id: 100, project_id: 1},
  178. {foo: "mock-update", v: 42, doc_id: 100, project_id: 1}
  179. ]
  180. @db.docHistory = {}
  181. @db.docHistory.find = sinon.stub().returns @db.docHistory
  182. @db.docHistory.sort = sinon.stub().returns @db.docHistory
  183. @db.docHistory.limit = sinon.stub().returns @db.docHistory
  184. @db.docHistory.toArray = sinon.stub().callsArgWith(0, null, @results)
  185. @from = 42
  186. @to = 55
  187. describe "with a to version", ->
  188. beforeEach ->
  189. @MongoManager.getDocUpdates @doc_id, from: @from, to: @to, @callback
  190. it "should find the all updates between the to and from versions", ->
  191. @db.docHistory.find
  192. .calledWith({
  193. doc_id: ObjectId(@doc_id)
  194. v: { $gte: @from, $lte: @to }
  195. })
  196. .should.equal true
  197. it "should sort in descending version order", ->
  198. @db.docHistory.sort
  199. .calledWith("v": -1)
  200. .should.equal true
  201. #it "should not limit the results", ->
  202. # @db.docHistory.limit
  203. # .called.should.equal false
  204. it "should call the call back with the results", ->
  205. @callback.calledWith(null, @updates_between).should.equal true
  206. describe "without a to version", ->
  207. beforeEach ->
  208. @MongoManager.getDocUpdates @doc_id, from: @from, @callback
  209. it "should find the all updates after the from version", ->
  210. @db.docHistory.find
  211. .calledWith({
  212. doc_id: ObjectId(@doc_id)
  213. v: { $gte: @from }
  214. })
  215. .should.equal true
  216. it "should call the call back with the updates", ->
  217. @callback.calledWith(null, @updates_after).should.equal true
  218. describe "with a limit", ->
  219. beforeEach ->
  220. @MongoManager.getDocUpdates @doc_id, from: @from, limit: @limit = 10, @callback
  221. it "should limit the results", ->
  222. @db.docHistory.limit
  223. .calledWith(@limit)
  224. .should.equal true
  225. describe "getDocUpdates", ->
  226. beforeEach ->
  227. @results = [
  228. {foo: "mock-update", v: 56, meta: {end_ts: 110}, doc_id: 100, project_id: 1},
  229. {foo: "mock-update", v: 55, meta: {end_ts: 100}, doc_id: 100, project_id: 1},
  230. {pack: [
  231. {foo: "mock-update", v: 54, meta: {end_ts: 99}, doc_id: 300, project_id: 1},
  232. {foo: "mock-update", v: 53, meta: {end_ts: 98}, doc_id: 300, project_id: 1},
  233. {foo: "mock-update", v: 52, meta: {end_ts: 97}, doc_id: 300, project_id: 1} ]
  234. , v: 52, meta: {end_ts: 100}, doc_id: 300, project_id: 1},
  235. {pack: [
  236. {foo: "mock-update", v: 54, meta: {end_ts: 103}, doc_id: 200, project_id: 1},
  237. {foo: "mock-update", v: 53, meta: {end_ts: 101}, doc_id: 200, project_id: 1},
  238. {foo: "mock-update", v: 52, meta: {end_ts: 99}, doc_id: 200, project_id: 1} ]
  239. , v: 52, meta: {end_ts: 103}, doc_id: 200, project_id: 1},
  240. {foo: "mock-update", v: 42, meta:{end_ts: 90}, doc_id: 100, project_id: 1}
  241. ]
  242. @updates_before = [
  243. {foo: "mock-update", v: 55, meta: {end_ts: 100}, doc_id: 100, project_id: 1},
  244. {foo: "mock-update", v: 52, meta: {end_ts: 99}, doc_id: 200, project_id: 1},
  245. {foo: "mock-update", v: 54, meta: {end_ts: 99}, doc_id: 300, project_id: 1},
  246. {foo: "mock-update", v: 53, meta: {end_ts: 98}, doc_id: 300, project_id: 1},
  247. {foo: "mock-update", v: 52, meta: {end_ts: 97}, doc_id: 300, project_id: 1},
  248. {foo: "mock-update", v: 42, meta: {end_ts: 90}, doc_id: 100, project_id: 1},
  249. ]
  250. @updates_all = [
  251. {foo: "mock-update", v: 56, meta: {end_ts: 110}, doc_id: 100, project_id: 1},
  252. {foo: "mock-update", v: 54, meta: {end_ts: 103}, doc_id: 200, project_id: 1},
  253. {foo: "mock-update", v: 53, meta: {end_ts: 101}, doc_id: 200, project_id: 1},
  254. {foo: "mock-update", v: 55, meta: {end_ts: 100}, doc_id: 100, project_id: 1},
  255. {foo: "mock-update", v: 52, meta: {end_ts: 99}, doc_id: 200, project_id: 1},
  256. {foo: "mock-update", v: 54, meta: {end_ts: 99}, doc_id: 300, project_id: 1},
  257. {foo: "mock-update", v: 53, meta: {end_ts: 98}, doc_id: 300, project_id: 1},
  258. {foo: "mock-update", v: 52, meta: {end_ts: 97}, doc_id: 300, project_id: 1},
  259. {foo: "mock-update", v: 42, meta: {end_ts: 90}, doc_id: 100, project_id: 1}
  260. ]
  261. @db.docHistory = {}
  262. @db.docHistory.find = sinon.stub().returns @db.docHistory
  263. @db.docHistory.sort = sinon.stub().returns @db.docHistory
  264. @db.docHistory.limit = sinon.stub().returns @db.docHistory
  265. @db.docHistory.toArray = sinon.stub().callsArgWith(0, null, @results)
  266. @before = 101
  267. describe "with a before timestamp", ->
  268. beforeEach ->
  269. @MongoManager.getProjectUpdates @project_id, before: @before, @callback
  270. it "should find the all updates before the timestamp", ->
  271. @db.docHistory.find
  272. .calledWith({
  273. project_id: ObjectId(@project_id)
  274. "meta.end_ts": { $lt: @before }
  275. })
  276. .should.equal true
  277. it "should sort in descending version order", ->
  278. @db.docHistory.sort
  279. .calledWith("meta.end_ts": -1)
  280. .should.equal true
  281. it "should not limit the results", ->
  282. @db.docHistory.limit
  283. .called.should.equal false
  284. it "should call the call back with the updates", ->
  285. @callback.calledWith(null, @updates_before).should.equal true
  286. describe "without a before timestamp", ->
  287. beforeEach ->
  288. @MongoManager.getProjectUpdates @project_id, {}, @callback
  289. it "should find the all updates", ->
  290. @db.docHistory.find
  291. .calledWith({
  292. project_id: ObjectId(@project_id)
  293. })
  294. .should.equal true
  295. it "should call the call back with the updates", ->
  296. @callback.calledWith(null, @updates_all).should.equal true
  297. describe "with a limit", ->
  298. beforeEach ->
  299. @MongoManager.getProjectUpdates @project_id, before: @before, limit: @limit = 10, @callback
  300. it "should limit the results", ->
  301. @db.docHistory.limit
  302. .calledWith(@limit)
  303. .should.equal true
  304. describe "backportProjectId", ->
  305. beforeEach ->
  306. @db.docHistory =
  307. update: sinon.stub().callsArg(3)
  308. @MongoManager.backportProjectId @project_id, @doc_id, @callback
  309. it "should insert the project_id into all entries for the doc_id which don't have it set", ->
  310. @db.docHistory.update
  311. .calledWith({
  312. doc_id: ObjectId(@doc_id)
  313. project_id: { $exists: false }
  314. }, {
  315. $set: { project_id: ObjectId(@project_id) }
  316. }, {
  317. multi: true
  318. })
  319. .should.equal true
  320. it "should call the callback", ->
  321. @callback.called.should.equal true
  322. describe "getProjectMetaData", ->
  323. beforeEach ->
  324. @metadata = { "mock": "metadata" }
  325. @db.projectHistoryMetaData =
  326. find: sinon.stub().callsArgWith(1, null, [@metadata])
  327. @MongoManager.getProjectMetaData @project_id, @callback
  328. it "should look up the meta data in the db", ->
  329. @db.projectHistoryMetaData.find
  330. .calledWith({ project_id: ObjectId(@project_id) })
  331. .should.equal true
  332. it "should return the metadata", ->
  333. @callback.calledWith(null, @metadata).should.equal true
  334. describe "setProjectMetaData", ->
  335. beforeEach ->
  336. @metadata = { "mock": "metadata" }
  337. @db.projectHistoryMetaData =
  338. update: sinon.stub().callsArgWith(3, null, [@metadata])
  339. @MongoManager.setProjectMetaData @project_id, @metadata, @callback
  340. it "should upsert the metadata into the DB", ->
  341. @db.projectHistoryMetaData.update
  342. .calledWith({
  343. project_id: ObjectId(@project_id)
  344. }, {
  345. $set: @metadata
  346. }, {
  347. upsert: true
  348. })
  349. .should.equal true
  350. it "should call the callback", ->
  351. @callback.called.should.equal true
  352. describe "getDocChangesCount", ->
  353. beforeEach ->
  354. @db.docHistory =
  355. count: sinon.stub().callsArg(2)
  356. @MongoManager.getDocChangesCount @doc_id, @callback
  357. it "should return if there is any doc changes", ->
  358. @db.docHistory.count
  359. .calledWith({
  360. doc_id: ObjectId(@doc_id)
  361. inS3 : { $exists : false }
  362. }, {
  363. })
  364. .should.equal true
  365. it "should call the callback", ->
  366. @callback.called.should.equal true
  367. describe "getArchivedDocChanges", ->
  368. beforeEach ->
  369. @db.docHistory =
  370. count: sinon.stub().callsArg(2)
  371. @MongoManager.getArchivedDocChanges @doc_id, @callback
  372. it "should return if there is any archived doc changes", ->
  373. @db.docHistory.count
  374. .calledWith({
  375. doc_id: ObjectId(@doc_id)
  376. inS3: {$exists: true}
  377. }, {
  378. })
  379. .should.equal true
  380. it "should call the callback", ->
  381. @callback.called.should.equal true
  382. describe "markDocHistoryAsArchived", ->
  383. beforeEach ->
  384. @update = { _id: ObjectId(), op: "op", meta: "meta", v: "v"}
  385. @db.docHistory =
  386. update: sinon.stub().callsArg(2)
  387. remove: sinon.stub().callsArg(1)
  388. @MongoManager.markDocHistoryAsArchived @doc_id, @update, @callback
  389. it "should update last doc change with inS3 flag", ->
  390. @db.docHistory.update
  391. .calledWith({
  392. _id: ObjectId(@update._id)
  393. },{
  394. $set : { inS3 : true }
  395. })
  396. .should.equal true
  397. it "should remove any other doc changes before last update", ->
  398. @db.docHistory.remove
  399. .calledWith({
  400. doc_id: ObjectId(@doc_id)
  401. inS3 : { $exists : false }
  402. v: { $lt : @update.v }
  403. expiresAt: {$exists : false}
  404. })
  405. .should.equal true
  406. it "should call the callback", ->
  407. @callback.called.should.equal true
  408. describe "markDocHistoryAsUnarchived", ->
  409. beforeEach ->
  410. @db.docHistory =
  411. update: sinon.stub().callsArg(3)
  412. @MongoManager.markDocHistoryAsUnarchived @doc_id, @callback
  413. it "should remove any doc changes inS3 flag", ->
  414. @db.docHistory.update
  415. .calledWith({
  416. doc_id: ObjectId(@doc_id)
  417. },{
  418. $unset : { inS3 : true }
  419. },{
  420. multi: true
  421. })
  422. .should.equal true
  423. it "should call the callback", ->
  424. @callback.called.should.equal true