HistoryManagerTests.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /* eslint-disable
  2. mocha/no-nested-tests,
  3. */
  4. const SandboxedModule = require('sandboxed-module')
  5. const sinon = require('sinon')
  6. const modulePath = require('node:path').join(
  7. __dirname,
  8. '../../../../app/js/HistoryManager'
  9. )
  10. describe('HistoryManager', function () {
  11. beforeEach(function () {
  12. this.HistoryManager = SandboxedModule.require(modulePath, {
  13. requires: {
  14. request: (this.request = {}),
  15. '@overleaf/fetch-utils': (this.fetchUtils = {
  16. fetchNothing: sinon.stub().resolves(),
  17. }),
  18. '@overleaf/settings': (this.Settings = {
  19. shortHistoryQueues: [],
  20. apis: {
  21. project_history: {
  22. url: 'http://project_history.example.com',
  23. },
  24. },
  25. }),
  26. './DocumentManager': (this.DocumentManager = {
  27. promises: {
  28. resyncDocContentsWithLock: sinon.stub().resolves(),
  29. },
  30. }),
  31. './RedisManager': (this.RedisManager = {}),
  32. './ProjectHistoryRedisManager': (this.ProjectHistoryRedisManager = {
  33. promises: {
  34. queueResyncProjectStructure: sinon.stub().resolves(),
  35. },
  36. }),
  37. './Metrics': (this.metrics = { inc: sinon.stub() }),
  38. },
  39. })
  40. this.project_id = 'mock-project-id'
  41. })
  42. describe('flushProjectChangesAsync', function () {
  43. beforeEach(function () {
  44. this.HistoryManager.flushProjectChangesAsync(this.project_id)
  45. })
  46. it('should send a request to the project history api', function () {
  47. this.fetchUtils.fetchNothing.should.have.been.calledWith(
  48. new URL(
  49. `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush?background=true`
  50. )
  51. )
  52. })
  53. })
  54. describe('flushProjectChanges', function () {
  55. describe('in the normal case', function () {
  56. beforeEach(async function () {
  57. await this.HistoryManager.promises.flushProjectChanges(
  58. this.project_id,
  59. {
  60. background: true,
  61. }
  62. )
  63. })
  64. it('should send a request to the project history api', function () {
  65. this.fetchUtils.fetchNothing.should.have.been.calledWith(
  66. new URL(
  67. `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush?background=true`
  68. )
  69. )
  70. })
  71. })
  72. describe('with the skip_history_flush option', function () {
  73. beforeEach(async function () {
  74. await this.HistoryManager.promises.flushProjectChanges(
  75. this.project_id,
  76. {
  77. skip_history_flush: true,
  78. }
  79. )
  80. })
  81. it('should not send a request to the project history api', function () {
  82. this.fetchUtils.fetchNothing.should.not.have.been.called
  83. })
  84. })
  85. })
  86. describe('recordAndFlushHistoryOps', function () {
  87. beforeEach(function () {
  88. this.ops = ['mock-ops']
  89. this.project_ops_length = 500
  90. this.HistoryManager.flushProjectChangesAsync = sinon.stub()
  91. })
  92. describe('with no ops', function () {
  93. beforeEach(function () {
  94. this.HistoryManager.recordAndFlushHistoryOps(
  95. this.project_id,
  96. [],
  97. this.project_ops_length
  98. )
  99. })
  100. it('should not flush project changes', function () {
  101. this.fetchUtils.fetchNothing.should.not.have.been.called
  102. })
  103. })
  104. describe('with enough ops to flush project changes', function () {
  105. beforeEach(function () {
  106. this.HistoryManager.recordAndFlushHistoryOps(
  107. this.project_id,
  108. this.ops,
  109. this.project_ops_length
  110. )
  111. })
  112. it('should flush project changes', function () {
  113. this.fetchUtils.fetchNothing.should.have.been.calledWith(
  114. new URL(
  115. `${this.Settings.apis.project_history.url}/project/${this.project_id}/flush?background=true`
  116. )
  117. )
  118. })
  119. })
  120. describe('shouldFlushHistoryOps', function () {
  121. it('should return false if the number of ops is not known', function () {
  122. this.HistoryManager.shouldFlushHistoryOps(
  123. this.project_id,
  124. null,
  125. ['a', 'b', 'c'].length,
  126. 1
  127. ).should.equal(false)
  128. })
  129. it("should return false if the updates didn't take us past the threshold", function () {
  130. // Currently there are 14 ops
  131. // Previously we were on 11 ops
  132. // We didn't pass over a multiple of 5
  133. this.HistoryManager.shouldFlushHistoryOps(
  134. this.project_id,
  135. 14,
  136. ['a', 'b', 'c'].length,
  137. 5
  138. ).should.equal(false)
  139. it('should return true if the updates took to the threshold', function () {})
  140. // Currently there are 15 ops
  141. // Previously we were on 12 ops
  142. // We've reached a new multiple of 5
  143. this.HistoryManager.shouldFlushHistoryOps(
  144. this.project_id,
  145. 15,
  146. ['a', 'b', 'c'].length,
  147. 5
  148. ).should.equal(true)
  149. })
  150. it('should return true if the updates took past the threshold', function () {
  151. // Currently there are 19 ops
  152. // Previously we were on 16 ops
  153. // We didn't pass over a multiple of 5
  154. this.HistoryManager.shouldFlushHistoryOps(
  155. this.project_id,
  156. 17,
  157. ['a', 'b', 'c'].length,
  158. 5
  159. ).should.equal(true)
  160. })
  161. it('should return true if the project has a short queue', function () {
  162. this.Settings.shortHistoryQueues = [this.project_id]
  163. this.HistoryManager.shouldFlushHistoryOps(
  164. this.project_id,
  165. 14,
  166. ['a', 'b', 'c'].length,
  167. 5
  168. ).should.equal(true)
  169. })
  170. })
  171. })
  172. describe('resyncProjectHistory', function () {
  173. beforeEach(function () {
  174. this.projectHistoryId = 'history-id-1234'
  175. this.docs = [
  176. {
  177. doc: this.doc_id,
  178. path: 'main.tex',
  179. },
  180. ]
  181. this.files = [
  182. {
  183. file: 'mock-file-id',
  184. path: 'universe.png',
  185. url: `www.filestore.test/${this.project_id}/mock-file-id`,
  186. },
  187. ]
  188. })
  189. describe('full sync', function () {
  190. beforeEach(async function () {
  191. await this.HistoryManager.promises.resyncProjectHistory(
  192. this.project_id,
  193. this.projectHistoryId,
  194. this.docs,
  195. this.files,
  196. {}
  197. )
  198. })
  199. it('should queue a project structure reync', function () {
  200. this.ProjectHistoryRedisManager.promises.queueResyncProjectStructure.should.have.been.calledWith(
  201. this.project_id,
  202. this.projectHistoryId,
  203. this.docs,
  204. this.files
  205. )
  206. })
  207. it('should queue doc content reyncs', function () {
  208. this.DocumentManager.promises.resyncDocContentsWithLock.should.have.been.calledWith(
  209. this.project_id,
  210. this.docs[0].doc,
  211. this.docs[0].path
  212. )
  213. })
  214. })
  215. describe('resyncProjectStructureOnly=true', function () {
  216. beforeEach(async function () {
  217. await this.HistoryManager.promises.resyncProjectHistory(
  218. this.project_id,
  219. this.projectHistoryId,
  220. this.docs,
  221. this.files,
  222. { resyncProjectStructureOnly: true }
  223. )
  224. })
  225. it('should queue a project structure reync', function () {
  226. this.ProjectHistoryRedisManager.promises.queueResyncProjectStructure.should.have.been.calledWith(
  227. this.project_id,
  228. this.projectHistoryId,
  229. this.docs,
  230. this.files,
  231. { resyncProjectStructureOnly: true }
  232. )
  233. })
  234. it('should not queue doc content reyncs', function () {
  235. this.DocumentManager.promises.resyncDocContentsWithLock.should.not.have
  236. .been.called
  237. })
  238. })
  239. })
  240. })