DiffManagerTests.js 14 KB

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