HistoryManagerTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. /* eslint-disable
  2. mocha/no-nested-tests,
  3. no-return-assign,
  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 SandboxedModule = require('sandboxed-module')
  13. const sinon = require('sinon')
  14. const modulePath = require('path').join(
  15. __dirname,
  16. '../../../../app/js/HistoryManager'
  17. )
  18. describe('HistoryManager', function () {
  19. beforeEach(function () {
  20. this.HistoryManager = SandboxedModule.require(modulePath, {
  21. requires: {
  22. request: (this.request = {}),
  23. '@overleaf/settings': (this.Settings = {
  24. apis: {
  25. project_history: {
  26. enabled: true,
  27. url: 'http://project_history.example.com',
  28. },
  29. trackchanges: {
  30. url: 'http://trackchanges.example.com',
  31. },
  32. },
  33. }),
  34. './DocumentManager': (this.DocumentManager = {}),
  35. './HistoryRedisManager': (this.HistoryRedisManager = {}),
  36. './RedisManager': (this.RedisManager = {}),
  37. './ProjectHistoryRedisManager': (this.ProjectHistoryRedisManager = {}),
  38. './Metrics': (this.metrics = { inc: sinon.stub() }),
  39. },
  40. })
  41. this.project_id = 'mock-project-id'
  42. this.doc_id = 'mock-doc-id'
  43. return (this.callback = sinon.stub())
  44. })
  45. describe('flushDocChangesAsync', function () {
  46. beforeEach(function () {
  47. return (this.request.post = sinon
  48. .stub()
  49. .callsArgWith(1, null, { statusCode: 204 }))
  50. })
  51. describe('when the project uses track changes', function () {
  52. beforeEach(function () {
  53. this.RedisManager.getHistoryType = sinon
  54. .stub()
  55. .yields(null, 'track-changes')
  56. return this.HistoryManager.flushDocChangesAsync(
  57. this.project_id,
  58. this.doc_id
  59. )
  60. })
  61. return it('should send a request to the track changes api', function () {
  62. return this.request.post
  63. .calledWith(
  64. `${this.Settings.apis.trackchanges.url}/project/${this.project_id}/doc/${this.doc_id}/flush`
  65. )
  66. .should.equal(true)
  67. })
  68. })
  69. describe('when the project uses project history and double flush is not disabled', function () {
  70. beforeEach(function () {
  71. this.RedisManager.getHistoryType = sinon
  72. .stub()
  73. .yields(null, 'project-history')
  74. return this.HistoryManager.flushDocChangesAsync(
  75. this.project_id,
  76. this.doc_id
  77. )
  78. })
  79. return it('should send a request to the track changes api', function () {
  80. return this.request.post.called.should.equal(true)
  81. })
  82. })
  83. return describe('when the project uses project history and double flush is disabled', function () {
  84. beforeEach(function () {
  85. this.Settings.disableDoubleFlush = true
  86. this.RedisManager.getHistoryType = sinon
  87. .stub()
  88. .yields(null, 'project-history')
  89. return this.HistoryManager.flushDocChangesAsync(
  90. this.project_id,
  91. this.doc_id
  92. )
  93. })
  94. return it('should not send a request to the track changes api', function () {
  95. return this.request.post.called.should.equal(false)
  96. })
  97. })
  98. })
  99. describe('flushProjectChangesAsync', function () {
  100. beforeEach(function () {
  101. this.request.post = sinon
  102. .stub()
  103. .callsArgWith(1, null, { statusCode: 204 })
  104. return this.HistoryManager.flushProjectChangesAsync(this.project_id)
  105. })
  106. return it('should send a request to the project history api', function () {
  107. return this.request.post
  108. .calledWith({
  109. url: `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush`,
  110. qs: { background: true },
  111. })
  112. .should.equal(true)
  113. })
  114. })
  115. describe('flushProjectChanges', function () {
  116. describe('in the normal case', function () {
  117. beforeEach(function (done) {
  118. this.request.post = sinon
  119. .stub()
  120. .callsArgWith(1, null, { statusCode: 204 })
  121. return this.HistoryManager.flushProjectChanges(
  122. this.project_id,
  123. {
  124. background: true,
  125. },
  126. done
  127. )
  128. })
  129. return it('should send a request to the project history api', function () {
  130. return this.request.post
  131. .calledWith({
  132. url: `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush`,
  133. qs: { background: true },
  134. })
  135. .should.equal(true)
  136. })
  137. })
  138. return describe('with the skip_history_flush option', function () {
  139. beforeEach(function (done) {
  140. this.request.post = sinon.stub()
  141. return this.HistoryManager.flushProjectChanges(
  142. this.project_id,
  143. {
  144. skip_history_flush: true,
  145. },
  146. done
  147. )
  148. })
  149. return it('should not send a request to the project history api', function () {
  150. return this.request.post.called.should.equal(false)
  151. })
  152. })
  153. })
  154. describe('recordAndFlushHistoryOps', function () {
  155. beforeEach(function () {
  156. this.ops = ['mock-ops']
  157. this.project_ops_length = 10
  158. this.doc_ops_length = 5
  159. this.HistoryManager.flushProjectChangesAsync = sinon.stub()
  160. this.HistoryRedisManager.recordDocHasHistoryOps = sinon.stub().callsArg(3)
  161. return (this.HistoryManager.flushDocChangesAsync = sinon.stub())
  162. })
  163. describe('with no ops', function () {
  164. beforeEach(function () {
  165. return this.HistoryManager.recordAndFlushHistoryOps(
  166. this.project_id,
  167. this.doc_id,
  168. [],
  169. this.doc_ops_length,
  170. this.project_ops_length,
  171. this.callback
  172. )
  173. })
  174. it('should not flush project changes', function () {
  175. return this.HistoryManager.flushProjectChangesAsync.called.should.equal(
  176. false
  177. )
  178. })
  179. it('should not record doc has history ops', function () {
  180. return this.HistoryRedisManager.recordDocHasHistoryOps.called.should.equal(
  181. false
  182. )
  183. })
  184. it('should not flush doc changes', function () {
  185. return this.HistoryManager.flushDocChangesAsync.called.should.equal(
  186. false
  187. )
  188. })
  189. return it('should call the callback', function () {
  190. return this.callback.called.should.equal(true)
  191. })
  192. })
  193. describe('with enough ops to flush project changes', function () {
  194. beforeEach(function () {
  195. this.HistoryManager.shouldFlushHistoryOps = sinon.stub()
  196. this.HistoryManager.shouldFlushHistoryOps
  197. .withArgs(this.project_ops_length)
  198. .returns(true)
  199. this.HistoryManager.shouldFlushHistoryOps
  200. .withArgs(this.doc_ops_length)
  201. .returns(false)
  202. return this.HistoryManager.recordAndFlushHistoryOps(
  203. this.project_id,
  204. this.doc_id,
  205. this.ops,
  206. this.doc_ops_length,
  207. this.project_ops_length,
  208. this.callback
  209. )
  210. })
  211. it('should flush project changes', function () {
  212. return this.HistoryManager.flushProjectChangesAsync
  213. .calledWith(this.project_id)
  214. .should.equal(true)
  215. })
  216. it('should record doc has history ops', function () {
  217. return this.HistoryRedisManager.recordDocHasHistoryOps.calledWith(
  218. this.project_id,
  219. this.doc_id,
  220. this.ops
  221. )
  222. })
  223. it('should not flush doc changes', function () {
  224. return this.HistoryManager.flushDocChangesAsync.called.should.equal(
  225. false
  226. )
  227. })
  228. return it('should call the callback', function () {
  229. return this.callback.called.should.equal(true)
  230. })
  231. })
  232. describe('with enough ops to flush doc changes', function () {
  233. beforeEach(function () {
  234. this.HistoryManager.shouldFlushHistoryOps = sinon.stub()
  235. this.HistoryManager.shouldFlushHistoryOps
  236. .withArgs(this.project_ops_length)
  237. .returns(false)
  238. this.HistoryManager.shouldFlushHistoryOps
  239. .withArgs(this.doc_ops_length)
  240. .returns(true)
  241. return this.HistoryManager.recordAndFlushHistoryOps(
  242. this.project_id,
  243. this.doc_id,
  244. this.ops,
  245. this.doc_ops_length,
  246. this.project_ops_length,
  247. this.callback
  248. )
  249. })
  250. it('should not flush project changes', function () {
  251. return this.HistoryManager.flushProjectChangesAsync.called.should.equal(
  252. false
  253. )
  254. })
  255. it('should record doc has history ops', function () {
  256. return this.HistoryRedisManager.recordDocHasHistoryOps.calledWith(
  257. this.project_id,
  258. this.doc_id,
  259. this.ops
  260. )
  261. })
  262. it('should flush doc changes', function () {
  263. return this.HistoryManager.flushDocChangesAsync
  264. .calledWith(this.project_id, this.doc_id)
  265. .should.equal(true)
  266. })
  267. return it('should call the callback', function () {
  268. return this.callback.called.should.equal(true)
  269. })
  270. })
  271. describe('when recording doc has history ops errors', function () {
  272. beforeEach(function () {
  273. this.error = new Error('error')
  274. this.HistoryRedisManager.recordDocHasHistoryOps = sinon
  275. .stub()
  276. .callsArgWith(3, this.error)
  277. return this.HistoryManager.recordAndFlushHistoryOps(
  278. this.project_id,
  279. this.doc_id,
  280. this.ops,
  281. this.doc_ops_length,
  282. this.project_ops_length,
  283. this.callback
  284. )
  285. })
  286. it('should not flush doc changes', function () {
  287. return this.HistoryManager.flushDocChangesAsync.called.should.equal(
  288. false
  289. )
  290. })
  291. return it('should call the callback with the error', function () {
  292. return this.callback.calledWith(this.error).should.equal(true)
  293. })
  294. })
  295. return describe('shouldFlushHistoryOps', function () {
  296. it('should return false if the number of ops is not known', function () {
  297. return this.HistoryManager.shouldFlushHistoryOps(
  298. null,
  299. ['a', 'b', 'c'].length,
  300. 1
  301. ).should.equal(false)
  302. })
  303. it("should return false if the updates didn't take us past the threshold", function () {
  304. // Currently there are 14 ops
  305. // Previously we were on 11 ops
  306. // We didn't pass over a multiple of 5
  307. this.HistoryManager.shouldFlushHistoryOps(
  308. 14,
  309. ['a', 'b', 'c'].length,
  310. 5
  311. ).should.equal(false)
  312. it('should return true if the updates took to the threshold', function () {})
  313. // Currently there are 15 ops
  314. // Previously we were on 12 ops
  315. // We've reached a new multiple of 5
  316. return this.HistoryManager.shouldFlushHistoryOps(
  317. 15,
  318. ['a', 'b', 'c'].length,
  319. 5
  320. ).should.equal(true)
  321. })
  322. return it('should return true if the updates took past the threshold', function () {
  323. // Currently there are 19 ops
  324. // Previously we were on 16 ops
  325. // We didn't pass over a multiple of 5
  326. return this.HistoryManager.shouldFlushHistoryOps(
  327. 17,
  328. ['a', 'b', 'c'].length,
  329. 5
  330. ).should.equal(true)
  331. })
  332. })
  333. })
  334. return describe('resyncProjectHistory', function () {
  335. beforeEach(function () {
  336. this.projectHistoryId = 'history-id-1234'
  337. this.docs = [
  338. {
  339. doc: this.doc_id,
  340. path: 'main.tex',
  341. },
  342. ]
  343. this.files = [
  344. {
  345. file: 'mock-file-id',
  346. path: 'universe.png',
  347. url: `www.filestore.test/${this.project_id}/mock-file-id`,
  348. },
  349. ]
  350. this.ProjectHistoryRedisManager.queueResyncProjectStructure = sinon
  351. .stub()
  352. .yields()
  353. this.DocumentManager.resyncDocContentsWithLock = sinon.stub().yields()
  354. return this.HistoryManager.resyncProjectHistory(
  355. this.project_id,
  356. this.projectHistoryId,
  357. this.docs,
  358. this.files,
  359. this.callback
  360. )
  361. })
  362. it('should queue a project structure reync', function () {
  363. return this.ProjectHistoryRedisManager.queueResyncProjectStructure
  364. .calledWith(
  365. this.project_id,
  366. this.projectHistoryId,
  367. this.docs,
  368. this.files
  369. )
  370. .should.equal(true)
  371. })
  372. it('should queue doc content reyncs', function () {
  373. return this.DocumentManager.resyncDocContentsWithLock
  374. .calledWith(this.project_id, this.docs[0].doc, this.docs[0].path)
  375. .should.equal(true)
  376. })
  377. return it('should call the callback', function () {
  378. return this.callback.called.should.equal(true)
  379. })
  380. })
  381. })