MongoManagerTests.coffee 16 KB

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