DiffManagerTests.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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. 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. this.DiffGenerator.rewindUpdates = sinon
  176. .stub()
  177. this.DiffManager.getDiff(
  178. this.project_id,
  179. this.doc_id,
  180. this.fromVersion,
  181. this.toVersion,
  182. this.callback
  183. )
  184. })
  185. it('should call the callback with an error', function() {
  186. this.callback.calledWith(sinon.match(Error)).should.equal(true)
  187. const errorObj = this.callback.args[0][0]
  188. expect(errorObj.message).to.include('inconsistent!')
  189. })
  190. })
  191. })
  192. describe('getDocumentBeforeVersion', function() {
  193. beforeEach(function() {
  194. this.DiffManager._tryGetDocumentBeforeVersion = sinon.stub()
  195. this.document = 'mock-documents'
  196. return (this.rewound_updates = 'mock-rewound-updates')
  197. })
  198. describe('succesfully', function() {
  199. beforeEach(function() {
  200. this.DiffManager._tryGetDocumentBeforeVersion.yields(
  201. null,
  202. this.document,
  203. this.rewound_updates
  204. )
  205. return this.DiffManager.getDocumentBeforeVersion(
  206. this.project_id,
  207. this.doc_id,
  208. this.version,
  209. this.callback
  210. )
  211. })
  212. it('should call _tryGetDocumentBeforeVersion', function() {
  213. return this.DiffManager._tryGetDocumentBeforeVersion
  214. .calledWith(this.project_id, this.doc_id, this.version)
  215. .should.equal(true)
  216. })
  217. return it('should call the callback with the response', function() {
  218. return this.callback
  219. .calledWith(null, this.document, this.rewound_updates)
  220. .should.equal(true)
  221. })
  222. })
  223. describe('with a retry needed', function() {
  224. beforeEach(function() {
  225. let retried = false
  226. this.DiffManager._tryGetDocumentBeforeVersion = (
  227. project_id,
  228. doc_id,
  229. version,
  230. callback
  231. ) => {
  232. if (!retried) {
  233. retried = true
  234. const error = new Error()
  235. error.retry = true
  236. return callback(error)
  237. } else {
  238. return callback(null, this.document, this.rewound_updates)
  239. }
  240. }
  241. sinon.spy(this.DiffManager, '_tryGetDocumentBeforeVersion')
  242. return this.DiffManager.getDocumentBeforeVersion(
  243. this.project_id,
  244. this.doc_id,
  245. this.version,
  246. this.callback
  247. )
  248. })
  249. it('should call _tryGetDocumentBeforeVersion twice', function() {
  250. return this.DiffManager._tryGetDocumentBeforeVersion.calledTwice.should.equal(
  251. true
  252. )
  253. })
  254. return it('should call the callback with the response', function() {
  255. return this.callback
  256. .calledWith(null, this.document, this.rewound_updates)
  257. .should.equal(true)
  258. })
  259. })
  260. describe('with a non-retriable error', function() {
  261. beforeEach(function() {
  262. this.error = new Error('oops')
  263. this.DiffManager._tryGetDocumentBeforeVersion.yields(this.error)
  264. return this.DiffManager.getDocumentBeforeVersion(
  265. this.project_id,
  266. this.doc_id,
  267. this.version,
  268. this.callback
  269. )
  270. })
  271. it('should call _tryGetDocumentBeforeVersion once', function() {
  272. return this.DiffManager._tryGetDocumentBeforeVersion.calledOnce.should.equal(
  273. true
  274. )
  275. })
  276. return it('should call the callback with the error', function() {
  277. return this.callback.calledWith(this.error).should.equal(true)
  278. })
  279. })
  280. return describe('when retry limit is matched', function() {
  281. beforeEach(function() {
  282. this.error = new Error('oops')
  283. this.error.retry = true
  284. this.DiffManager._tryGetDocumentBeforeVersion.yields(this.error)
  285. return this.DiffManager.getDocumentBeforeVersion(
  286. this.project_id,
  287. this.doc_id,
  288. this.version,
  289. this.callback
  290. )
  291. })
  292. it('should call _tryGetDocumentBeforeVersion three times (max retries)', function() {
  293. return this.DiffManager._tryGetDocumentBeforeVersion.calledThrice.should.equal(
  294. true
  295. )
  296. })
  297. return it('should call the callback with the error', function() {
  298. return this.callback.calledWith(this.error).should.equal(true)
  299. })
  300. })
  301. })
  302. return describe('_tryGetDocumentBeforeVersion', function() {
  303. beforeEach(function() {
  304. this.content = 'hello world'
  305. // Op versions are the version they were applied to, so doc is always one version
  306. // ahead.s
  307. this.version = 43
  308. this.updates = [
  309. {
  310. op: 'mock-4',
  311. v: 42,
  312. meta: { start_ts: new Date(this.to.getTime() + 20) }
  313. },
  314. {
  315. op: 'mock-3',
  316. v: 41,
  317. meta: { start_ts: new Date(this.to.getTime() + 10) }
  318. },
  319. {
  320. op: 'mock-2',
  321. v: 40,
  322. meta: { start_ts: new Date(this.to.getTime() - 10) }
  323. },
  324. {
  325. op: 'mock-1',
  326. v: 39,
  327. meta: { start_ts: new Date(this.to.getTime() - 20) }
  328. }
  329. ]
  330. this.fromVersion = 39
  331. this.rewound_content = 'rewound-content'
  332. return (this.diff = [{ u: 'mock-diff' }])
  333. })
  334. describe('with matching versions', function() {
  335. beforeEach(function() {
  336. this.DiffManager.getLatestDocAndUpdates = sinon
  337. .stub()
  338. .callsArgWith(3, null, this.content, this.version, this.updates)
  339. this.DiffGenerator.rewindUpdates = sinon.spy((content, updates) => {
  340. // the rewindUpdates method reverses the 'updates' array
  341. updates.reverse()
  342. return this.rewound_content
  343. })
  344. this.rewindUpdatesWithArgs = this.DiffGenerator.rewindUpdates.withArgs(
  345. this.content,
  346. this.updates.slice().reverse()
  347. )
  348. return this.DiffManager._tryGetDocumentBeforeVersion(
  349. this.project_id,
  350. this.doc_id,
  351. this.fromVersion,
  352. this.callback
  353. )
  354. })
  355. it('should get the latest doc and version with all recent updates', function() {
  356. return this.DiffManager.getLatestDocAndUpdates
  357. .calledWith(this.project_id, this.doc_id, this.fromVersion)
  358. .should.equal(true)
  359. })
  360. it('should rewind the diff', function() {
  361. return sinon.assert.calledOnce(this.rewindUpdatesWithArgs)
  362. })
  363. return it('should call the callback with the rewound document and updates', function() {
  364. return this.callback
  365. .calledWith(null, this.rewound_content, this.updates)
  366. .should.equal(true)
  367. })
  368. })
  369. describe('with mismatching versions', function() {
  370. beforeEach(function() {
  371. this.version = 50
  372. this.updates = [
  373. { op: 'mock-1', v: 40 },
  374. { op: 'mock-1', v: 39 }
  375. ]
  376. this.DiffManager.getLatestDocAndUpdates = sinon
  377. .stub()
  378. .callsArgWith(3, null, this.content, this.version, this.updates)
  379. return this.DiffManager._tryGetDocumentBeforeVersion(
  380. this.project_id,
  381. this.doc_id,
  382. this.fromVersion,
  383. this.callback
  384. )
  385. })
  386. return it('should call the callback with an error with retry = true set', function() {
  387. this.callback.calledOnce.should.equal(true)
  388. const error = this.callback.args[0][0]
  389. return expect(error.retry).to.equal(true)
  390. })
  391. })
  392. return describe('when the updates are inconsistent', function() {
  393. beforeEach(function() {
  394. this.DiffManager.getLatestDocAndUpdates = sinon
  395. .stub()
  396. .callsArgWith(3, null, this.content, this.version, this.updates)
  397. this.DiffGenerator.rewindUpdates = sinon
  398. .stub()
  399. .throws((this.error = new Error('inconsistent!')))
  400. return this.DiffManager.getDocumentBeforeVersion(
  401. this.project_id,
  402. this.doc_id,
  403. this.fromVersion,
  404. this.callback
  405. )
  406. })
  407. return it('should call the callback with an error', function() {
  408. return this.callback.calledWith(this.error).should.equal(true)
  409. })
  410. })
  411. })
  412. })