DiffManagerTests.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /* eslint-disable
  2. camelcase,
  3. no-return-assign,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const sinon = require('sinon')
  14. const { expect } = require('chai')
  15. const modulePath = '../../../../app/js/DiffManager.js'
  16. const SandboxedModule = require('sandboxed-module')
  17. describe('DiffManager', function () {
  18. beforeEach(function () {
  19. this.DiffManager = SandboxedModule.require(modulePath, {
  20. requires: {
  21. './UpdatesManager': (this.UpdatesManager = {}),
  22. './DocumentUpdaterManager': (this.DocumentUpdaterManager = {}),
  23. './DiffGenerator': (this.DiffGenerator = {}),
  24. },
  25. })
  26. this.callback = sinon.stub()
  27. this.from = new Date()
  28. this.to = new Date(Date.now() + 10000)
  29. this.project_id = 'mock-project-id'
  30. return (this.doc_id = 'mock-doc-id')
  31. })
  32. describe('getLatestDocAndUpdates', function () {
  33. beforeEach(function () {
  34. this.content = 'hello world'
  35. this.version = 42
  36. this.updates = ['mock-update-1', 'mock-update-2']
  37. this.DocumentUpdaterManager.getDocument = sinon
  38. .stub()
  39. .callsArgWith(2, null, this.content, this.version)
  40. return (this.UpdatesManager.getDocUpdatesWithUserInfo = sinon
  41. .stub()
  42. .callsArgWith(3, null, this.updates))
  43. })
  44. describe('with a fromVersion', function () {
  45. beforeEach(function () {
  46. return this.DiffManager.getLatestDocAndUpdates(
  47. this.project_id,
  48. this.doc_id,
  49. this.from,
  50. this.callback
  51. )
  52. })
  53. it('should get the latest version of the doc', function () {
  54. return this.DocumentUpdaterManager.getDocument
  55. .calledWith(this.project_id, this.doc_id)
  56. .should.equal(true)
  57. })
  58. it('should get the latest updates', function () {
  59. return this.UpdatesManager.getDocUpdatesWithUserInfo
  60. .calledWith(this.project_id, this.doc_id, { from: this.from })
  61. .should.equal(true)
  62. })
  63. return it('should call the callback with the content, version and updates', function () {
  64. return this.callback
  65. .calledWith(null, this.content, this.version, this.updates)
  66. .should.equal(true)
  67. })
  68. })
  69. return describe('with no fromVersion', function () {
  70. beforeEach(function () {
  71. return this.DiffManager.getLatestDocAndUpdates(
  72. this.project_id,
  73. this.doc_id,
  74. null,
  75. this.callback
  76. )
  77. })
  78. it('should get the latest version of the doc', function () {
  79. return this.DocumentUpdaterManager.getDocument
  80. .calledWith(this.project_id, this.doc_id)
  81. .should.equal(true)
  82. })
  83. it('should not get the latest updates', function () {
  84. return this.UpdatesManager.getDocUpdatesWithUserInfo.called.should.equal(
  85. false
  86. )
  87. })
  88. return it('should call the callback with the content, version and blank updates', function () {
  89. return this.callback
  90. .calledWith(null, this.content, this.version, [])
  91. .should.equal(true)
  92. })
  93. })
  94. })
  95. describe('getDiff', function () {
  96. beforeEach(function () {
  97. this.content = 'hello world'
  98. // Op versions are the version they were applied to, so doc is always one version
  99. // ahead.s
  100. this.version = 43
  101. this.updates = [
  102. {
  103. op: 'mock-4',
  104. v: 42,
  105. meta: { start_ts: new Date(this.to.getTime() + 20) },
  106. },
  107. {
  108. op: 'mock-3',
  109. v: 41,
  110. meta: { start_ts: new Date(this.to.getTime() + 10) },
  111. },
  112. {
  113. op: 'mock-2',
  114. v: 40,
  115. meta: { start_ts: new Date(this.to.getTime() - 10) },
  116. },
  117. {
  118. op: 'mock-1',
  119. v: 39,
  120. meta: { start_ts: new Date(this.to.getTime() - 20) },
  121. },
  122. ]
  123. this.fromVersion = 39
  124. this.toVersion = 40
  125. this.diffed_updates = this.updates.slice(2)
  126. this.rewound_content = 'rewound-content'
  127. return (this.diff = [{ u: 'mock-diff' }])
  128. })
  129. describe('with matching versions', function () {
  130. beforeEach(function () {
  131. this.DiffManager.getDocumentBeforeVersion = sinon
  132. .stub()
  133. .callsArgWith(3, null, this.rewound_content, this.updates)
  134. this.DiffGenerator.buildDiff = sinon.stub().returns(this.diff)
  135. return this.DiffManager.getDiff(
  136. this.project_id,
  137. this.doc_id,
  138. this.fromVersion,
  139. this.toVersion,
  140. this.callback
  141. )
  142. })
  143. it('should get the latest doc and version with all recent updates', function () {
  144. return this.DiffManager.getDocumentBeforeVersion
  145. .calledWith(this.project_id, this.doc_id, this.fromVersion)
  146. .should.equal(true)
  147. })
  148. it('should generate the diff', function () {
  149. return this.DiffGenerator.buildDiff
  150. .calledWith(
  151. this.rewound_content,
  152. this.diffed_updates.slice().reverse()
  153. )
  154. .should.equal(true)
  155. })
  156. return it('should call the callback with the diff', function () {
  157. return this.callback.calledWith(null, this.diff).should.equal(true)
  158. })
  159. })
  160. describe('when the updates are inconsistent', function () {
  161. beforeEach(function () {
  162. this.DiffManager.getLatestDocAndUpdates = sinon
  163. .stub()
  164. .callsArgWith(3, null, this.content, this.version, this.updates)
  165. this.DiffGenerator.buildDiff = sinon
  166. .stub()
  167. .throws((this.error = new Error('inconsistent!')))
  168. this.DiffGenerator.rewindUpdates = sinon.stub()
  169. this.DiffManager.getDiff(
  170. this.project_id,
  171. this.doc_id,
  172. this.fromVersion,
  173. this.toVersion,
  174. this.callback
  175. )
  176. })
  177. it('should call the callback with an error', function () {
  178. this.callback.calledWith(sinon.match(Error)).should.equal(true)
  179. const errorObj = this.callback.args[0][0]
  180. expect(errorObj.message).to.include('inconsistent!')
  181. })
  182. })
  183. })
  184. describe('getDocumentBeforeVersion', function () {
  185. beforeEach(function () {
  186. this.DiffManager._tryGetDocumentBeforeVersion = sinon.stub()
  187. this.document = 'mock-documents'
  188. return (this.rewound_updates = 'mock-rewound-updates')
  189. })
  190. describe('succesfully', function () {
  191. beforeEach(function () {
  192. this.DiffManager._tryGetDocumentBeforeVersion.yields(
  193. null,
  194. this.document,
  195. this.rewound_updates
  196. )
  197. return this.DiffManager.getDocumentBeforeVersion(
  198. this.project_id,
  199. this.doc_id,
  200. this.version,
  201. this.callback
  202. )
  203. })
  204. it('should call _tryGetDocumentBeforeVersion', function () {
  205. return this.DiffManager._tryGetDocumentBeforeVersion
  206. .calledWith(this.project_id, this.doc_id, this.version)
  207. .should.equal(true)
  208. })
  209. return it('should call the callback with the response', function () {
  210. return this.callback
  211. .calledWith(null, this.document, this.rewound_updates)
  212. .should.equal(true)
  213. })
  214. })
  215. describe('with a retry needed', function () {
  216. beforeEach(function () {
  217. let retried = false
  218. this.DiffManager._tryGetDocumentBeforeVersion = (
  219. project_id,
  220. doc_id,
  221. version,
  222. callback
  223. ) => {
  224. if (!retried) {
  225. retried = true
  226. const error = new Error()
  227. error.retry = true
  228. return callback(error)
  229. } else {
  230. return callback(null, this.document, this.rewound_updates)
  231. }
  232. }
  233. sinon.spy(this.DiffManager, '_tryGetDocumentBeforeVersion')
  234. return this.DiffManager.getDocumentBeforeVersion(
  235. this.project_id,
  236. this.doc_id,
  237. this.version,
  238. this.callback
  239. )
  240. })
  241. it('should call _tryGetDocumentBeforeVersion twice', function () {
  242. return this.DiffManager._tryGetDocumentBeforeVersion.calledTwice.should.equal(
  243. true
  244. )
  245. })
  246. return it('should call the callback with the response', function () {
  247. return this.callback
  248. .calledWith(null, this.document, this.rewound_updates)
  249. .should.equal(true)
  250. })
  251. })
  252. describe('with a non-retriable error', function () {
  253. beforeEach(function () {
  254. this.error = new Error('oops')
  255. this.DiffManager._tryGetDocumentBeforeVersion.yields(this.error)
  256. return this.DiffManager.getDocumentBeforeVersion(
  257. this.project_id,
  258. this.doc_id,
  259. this.version,
  260. this.callback
  261. )
  262. })
  263. it('should call _tryGetDocumentBeforeVersion once', function () {
  264. return this.DiffManager._tryGetDocumentBeforeVersion.calledOnce.should.equal(
  265. true
  266. )
  267. })
  268. return it('should call the callback with the error', function () {
  269. return this.callback.calledWith(this.error).should.equal(true)
  270. })
  271. })
  272. return describe('when retry limit is matched', function () {
  273. beforeEach(function () {
  274. this.error = new Error('oops')
  275. this.error.retry = true
  276. this.DiffManager._tryGetDocumentBeforeVersion.yields(this.error)
  277. return this.DiffManager.getDocumentBeforeVersion(
  278. this.project_id,
  279. this.doc_id,
  280. this.version,
  281. this.callback
  282. )
  283. })
  284. it('should call _tryGetDocumentBeforeVersion three times (max retries)', function () {
  285. return this.DiffManager._tryGetDocumentBeforeVersion.calledThrice.should.equal(
  286. true
  287. )
  288. })
  289. return it('should call the callback with the error', function () {
  290. return this.callback.calledWith(this.error).should.equal(true)
  291. })
  292. })
  293. })
  294. return describe('_tryGetDocumentBeforeVersion', function () {
  295. beforeEach(function () {
  296. this.content = 'hello world'
  297. // Op versions are the version they were applied to, so doc is always one version
  298. // ahead.s
  299. this.version = 43
  300. this.updates = [
  301. {
  302. op: 'mock-4',
  303. v: 42,
  304. meta: { start_ts: new Date(this.to.getTime() + 20) },
  305. },
  306. {
  307. op: 'mock-3',
  308. v: 41,
  309. meta: { start_ts: new Date(this.to.getTime() + 10) },
  310. },
  311. {
  312. op: 'mock-2',
  313. v: 40,
  314. meta: { start_ts: new Date(this.to.getTime() - 10) },
  315. },
  316. {
  317. op: 'mock-1',
  318. v: 39,
  319. meta: { start_ts: new Date(this.to.getTime() - 20) },
  320. },
  321. ]
  322. this.fromVersion = 39
  323. this.rewound_content = 'rewound-content'
  324. return (this.diff = [{ u: 'mock-diff' }])
  325. })
  326. describe('with matching versions', function () {
  327. beforeEach(function () {
  328. this.DiffManager.getLatestDocAndUpdates = sinon
  329. .stub()
  330. .callsArgWith(3, null, this.content, this.version, this.updates)
  331. this.DiffGenerator.rewindUpdates = sinon.spy((content, updates) => {
  332. // the rewindUpdates method reverses the 'updates' array
  333. updates.reverse()
  334. return this.rewound_content
  335. })
  336. this.rewindUpdatesWithArgs = this.DiffGenerator.rewindUpdates.withArgs(
  337. this.content,
  338. this.updates.slice().reverse()
  339. )
  340. return this.DiffManager._tryGetDocumentBeforeVersion(
  341. this.project_id,
  342. this.doc_id,
  343. this.fromVersion,
  344. this.callback
  345. )
  346. })
  347. it('should get the latest doc and version with all recent updates', function () {
  348. return this.DiffManager.getLatestDocAndUpdates
  349. .calledWith(this.project_id, this.doc_id, this.fromVersion)
  350. .should.equal(true)
  351. })
  352. it('should rewind the diff', function () {
  353. return sinon.assert.calledOnce(this.rewindUpdatesWithArgs)
  354. })
  355. return it('should call the callback with the rewound document and updates', function () {
  356. return this.callback
  357. .calledWith(null, this.rewound_content, this.updates)
  358. .should.equal(true)
  359. })
  360. })
  361. describe('with mismatching versions', function () {
  362. beforeEach(function () {
  363. this.version = 50
  364. this.updates = [
  365. { op: 'mock-1', v: 40 },
  366. { op: 'mock-1', v: 39 },
  367. ]
  368. this.DiffManager.getLatestDocAndUpdates = sinon
  369. .stub()
  370. .callsArgWith(3, null, this.content, this.version, this.updates)
  371. return this.DiffManager._tryGetDocumentBeforeVersion(
  372. this.project_id,
  373. this.doc_id,
  374. this.fromVersion,
  375. this.callback
  376. )
  377. })
  378. return it('should call the callback with an error with retry = true set', function () {
  379. this.callback.calledOnce.should.equal(true)
  380. const error = this.callback.args[0][0]
  381. return expect(error.retry).to.equal(true)
  382. })
  383. })
  384. return describe('when the updates are inconsistent', function () {
  385. beforeEach(function () {
  386. this.DiffManager.getLatestDocAndUpdates = sinon
  387. .stub()
  388. .callsArgWith(3, null, this.content, this.version, this.updates)
  389. this.DiffGenerator.rewindUpdates = sinon
  390. .stub()
  391. .throws((this.error = new Error('inconsistent!')))
  392. return this.DiffManager.getDocumentBeforeVersion(
  393. this.project_id,
  394. this.doc_id,
  395. this.fromVersion,
  396. this.callback
  397. )
  398. })
  399. return it('should call the callback with an error', function () {
  400. return this.callback.calledWith(this.error).should.equal(true)
  401. })
  402. })
  403. })
  404. })