DiffManagerTests.js 14 KB

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