HistoryManagerTests.js 13 KB

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