HistoryManagerTests.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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 () {
  118. this.request.post = sinon
  119. .stub()
  120. .callsArgWith(1, null, { statusCode: 204 })
  121. return this.HistoryManager.flushProjectChanges(this.project_id, {
  122. background: true,
  123. })
  124. })
  125. return it('should send a request to the project history api', function () {
  126. return this.request.post
  127. .calledWith({
  128. url: `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush`,
  129. qs: { background: true },
  130. })
  131. .should.equal(true)
  132. })
  133. })
  134. return describe('with the skip_history_flush option', function () {
  135. beforeEach(function () {
  136. this.request.post = sinon.stub()
  137. return this.HistoryManager.flushProjectChanges(this.project_id, {
  138. skip_history_flush: true,
  139. })
  140. })
  141. return it('should not send a request to the project history api', function () {
  142. return this.request.post.called.should.equal(false)
  143. })
  144. })
  145. })
  146. describe('recordAndFlushHistoryOps', function () {
  147. beforeEach(function () {
  148. this.ops = ['mock-ops']
  149. this.project_ops_length = 10
  150. this.doc_ops_length = 5
  151. this.HistoryManager.flushProjectChangesAsync = sinon.stub()
  152. this.HistoryRedisManager.recordDocHasHistoryOps = sinon.stub().callsArg(3)
  153. return (this.HistoryManager.flushDocChangesAsync = sinon.stub())
  154. })
  155. describe('with no ops', function () {
  156. beforeEach(function () {
  157. return this.HistoryManager.recordAndFlushHistoryOps(
  158. this.project_id,
  159. this.doc_id,
  160. [],
  161. this.doc_ops_length,
  162. this.project_ops_length,
  163. this.callback
  164. )
  165. })
  166. it('should not flush project changes', function () {
  167. return this.HistoryManager.flushProjectChangesAsync.called.should.equal(
  168. false
  169. )
  170. })
  171. it('should not record doc has history ops', function () {
  172. return this.HistoryRedisManager.recordDocHasHistoryOps.called.should.equal(
  173. false
  174. )
  175. })
  176. it('should not flush doc changes', function () {
  177. return this.HistoryManager.flushDocChangesAsync.called.should.equal(
  178. false
  179. )
  180. })
  181. return it('should call the callback', function () {
  182. return this.callback.called.should.equal(true)
  183. })
  184. })
  185. describe('with enough ops to flush project changes', function () {
  186. beforeEach(function () {
  187. this.HistoryManager.shouldFlushHistoryOps = sinon.stub()
  188. this.HistoryManager.shouldFlushHistoryOps
  189. .withArgs(this.project_ops_length)
  190. .returns(true)
  191. this.HistoryManager.shouldFlushHistoryOps
  192. .withArgs(this.doc_ops_length)
  193. .returns(false)
  194. return this.HistoryManager.recordAndFlushHistoryOps(
  195. this.project_id,
  196. this.doc_id,
  197. this.ops,
  198. this.doc_ops_length,
  199. this.project_ops_length,
  200. this.callback
  201. )
  202. })
  203. it('should flush project changes', function () {
  204. return this.HistoryManager.flushProjectChangesAsync
  205. .calledWith(this.project_id)
  206. .should.equal(true)
  207. })
  208. it('should record doc has history ops', function () {
  209. return this.HistoryRedisManager.recordDocHasHistoryOps.calledWith(
  210. this.project_id,
  211. this.doc_id,
  212. this.ops
  213. )
  214. })
  215. it('should not flush doc changes', function () {
  216. return this.HistoryManager.flushDocChangesAsync.called.should.equal(
  217. false
  218. )
  219. })
  220. return it('should call the callback', function () {
  221. return this.callback.called.should.equal(true)
  222. })
  223. })
  224. describe('with enough ops to flush doc changes', function () {
  225. beforeEach(function () {
  226. this.HistoryManager.shouldFlushHistoryOps = sinon.stub()
  227. this.HistoryManager.shouldFlushHistoryOps
  228. .withArgs(this.project_ops_length)
  229. .returns(false)
  230. this.HistoryManager.shouldFlushHistoryOps
  231. .withArgs(this.doc_ops_length)
  232. .returns(true)
  233. return this.HistoryManager.recordAndFlushHistoryOps(
  234. this.project_id,
  235. this.doc_id,
  236. this.ops,
  237. this.doc_ops_length,
  238. this.project_ops_length,
  239. this.callback
  240. )
  241. })
  242. it('should not flush project changes', function () {
  243. return this.HistoryManager.flushProjectChangesAsync.called.should.equal(
  244. false
  245. )
  246. })
  247. it('should record doc has history ops', function () {
  248. return this.HistoryRedisManager.recordDocHasHistoryOps.calledWith(
  249. this.project_id,
  250. this.doc_id,
  251. this.ops
  252. )
  253. })
  254. it('should flush doc changes', function () {
  255. return this.HistoryManager.flushDocChangesAsync
  256. .calledWith(this.project_id, this.doc_id)
  257. .should.equal(true)
  258. })
  259. return it('should call the callback', function () {
  260. return this.callback.called.should.equal(true)
  261. })
  262. })
  263. describe('when recording doc has history ops errors', function () {
  264. beforeEach(function () {
  265. this.error = new Error('error')
  266. this.HistoryRedisManager.recordDocHasHistoryOps = sinon
  267. .stub()
  268. .callsArgWith(3, this.error)
  269. return this.HistoryManager.recordAndFlushHistoryOps(
  270. this.project_id,
  271. this.doc_id,
  272. this.ops,
  273. this.doc_ops_length,
  274. this.project_ops_length,
  275. this.callback
  276. )
  277. })
  278. it('should not flush doc changes', function () {
  279. return this.HistoryManager.flushDocChangesAsync.called.should.equal(
  280. false
  281. )
  282. })
  283. return it('should call the callback with the error', function () {
  284. return this.callback.calledWith(this.error).should.equal(true)
  285. })
  286. })
  287. return describe('shouldFlushHistoryOps', function () {
  288. it('should return false if the number of ops is not known', function () {
  289. return this.HistoryManager.shouldFlushHistoryOps(
  290. null,
  291. ['a', 'b', 'c'].length,
  292. 1
  293. ).should.equal(false)
  294. })
  295. it("should return false if the updates didn't take us past the threshold", function () {
  296. // Currently there are 14 ops
  297. // Previously we were on 11 ops
  298. // We didn't pass over a multiple of 5
  299. this.HistoryManager.shouldFlushHistoryOps(
  300. 14,
  301. ['a', 'b', 'c'].length,
  302. 5
  303. ).should.equal(false)
  304. it('should return true if the updates took to the threshold', function () {})
  305. // Currently there are 15 ops
  306. // Previously we were on 12 ops
  307. // We've reached a new multiple of 5
  308. return this.HistoryManager.shouldFlushHistoryOps(
  309. 15,
  310. ['a', 'b', 'c'].length,
  311. 5
  312. ).should.equal(true)
  313. })
  314. return it('should return true if the updates took past the threshold', function () {
  315. // Currently there are 19 ops
  316. // Previously we were on 16 ops
  317. // We didn't pass over a multiple of 5
  318. return this.HistoryManager.shouldFlushHistoryOps(
  319. 17,
  320. ['a', 'b', 'c'].length,
  321. 5
  322. ).should.equal(true)
  323. })
  324. })
  325. })
  326. return describe('resyncProjectHistory', function () {
  327. beforeEach(function () {
  328. this.projectHistoryId = 'history-id-1234'
  329. this.docs = [
  330. {
  331. doc: this.doc_id,
  332. path: 'main.tex',
  333. },
  334. ]
  335. this.files = [
  336. {
  337. file: 'mock-file-id',
  338. path: 'universe.png',
  339. url: `www.filestore.test/${this.project_id}/mock-file-id`,
  340. },
  341. ]
  342. this.ProjectHistoryRedisManager.queueResyncProjectStructure = sinon
  343. .stub()
  344. .yields()
  345. this.DocumentManager.resyncDocContentsWithLock = sinon.stub().yields()
  346. return this.HistoryManager.resyncProjectHistory(
  347. this.project_id,
  348. this.projectHistoryId,
  349. this.docs,
  350. this.files,
  351. this.callback
  352. )
  353. })
  354. it('should queue a project structure reync', function () {
  355. return this.ProjectHistoryRedisManager.queueResyncProjectStructure
  356. .calledWith(
  357. this.project_id,
  358. this.projectHistoryId,
  359. this.docs,
  360. this.files
  361. )
  362. .should.equal(true)
  363. })
  364. it('should queue doc content reyncs', function () {
  365. return this.DocumentManager.resyncDocContentsWithLock
  366. .calledWith(this.project_id, this.doc_id)
  367. .should.equal(true)
  368. })
  369. return it('should call the callback', function () {
  370. return this.callback.called.should.equal(true)
  371. })
  372. })
  373. })