ProjectHistoryRedisManagerTests.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. const sinon = require('sinon')
  15. const modulePath = '../../../../app/js/ProjectHistoryRedisManager.js'
  16. const SandboxedModule = require('sandboxed-module')
  17. const tk = require('timekeeper')
  18. describe('ProjectHistoryRedisManager', function () {
  19. beforeEach(function () {
  20. this.project_id = 'project-id-123'
  21. this.projectHistoryId = 'history-id-123'
  22. this.user_id = 'user-id-123'
  23. this.callback = sinon.stub()
  24. this.rclient = {}
  25. tk.freeze(new Date())
  26. this.Limits = {
  27. docIsTooLarge: sinon.stub().returns(false),
  28. }
  29. return (this.ProjectHistoryRedisManager = SandboxedModule.require(
  30. modulePath,
  31. {
  32. requires: {
  33. '@overleaf/settings': (this.settings = {
  34. max_doc_length: 123,
  35. redis: {
  36. project_history: {
  37. key_schema: {
  38. projectHistoryOps({ project_id }) {
  39. return `ProjectHistory:Ops:${project_id}`
  40. },
  41. projectHistoryFirstOpTimestamp({ project_id }) {
  42. return `ProjectHistory:FirstOpTimestamp:${project_id}`
  43. },
  44. },
  45. },
  46. },
  47. }),
  48. '@overleaf/redis-wrapper': {
  49. createClient: () => this.rclient,
  50. },
  51. './Metrics': (this.metrics = { summary: sinon.stub() }),
  52. './Limits': this.Limits,
  53. },
  54. }
  55. ))
  56. })
  57. afterEach(function () {
  58. return tk.reset()
  59. })
  60. describe('queueOps', function () {
  61. beforeEach(function () {
  62. this.ops = ['mock-op-1', 'mock-op-2']
  63. this.multi = { exec: sinon.stub() }
  64. this.multi.rpush = sinon.stub()
  65. this.multi.setnx = sinon.stub()
  66. this.rclient.multi = () => this.multi
  67. // @rclient = multi: () => @multi
  68. return this.ProjectHistoryRedisManager.queueOps(
  69. this.project_id,
  70. ...Array.from(this.ops),
  71. this.callback
  72. )
  73. })
  74. it('should queue an update', function () {
  75. return this.multi.rpush
  76. .calledWithExactly(
  77. `ProjectHistory:Ops:${this.project_id}`,
  78. this.ops[0],
  79. this.ops[1]
  80. )
  81. .should.equal(true)
  82. })
  83. return it('should set the queue timestamp if not present', function () {
  84. return this.multi.setnx
  85. .calledWithExactly(
  86. `ProjectHistory:FirstOpTimestamp:${this.project_id}`,
  87. Date.now()
  88. )
  89. .should.equal(true)
  90. })
  91. })
  92. describe('queueRenameEntity', function () {
  93. beforeEach(function () {
  94. this.file_id = 1234
  95. this.rawUpdate = {
  96. pathname: (this.pathname = '/old'),
  97. newPathname: (this.newPathname = '/new'),
  98. version: (this.version = 2),
  99. }
  100. this.ProjectHistoryRedisManager.queueOps = sinon.stub()
  101. return this.ProjectHistoryRedisManager.queueRenameEntity(
  102. this.project_id,
  103. this.projectHistoryId,
  104. 'file',
  105. this.file_id,
  106. this.user_id,
  107. this.rawUpdate,
  108. this.callback
  109. )
  110. })
  111. return it('should queue an update', function () {
  112. const update = {
  113. pathname: this.pathname,
  114. new_pathname: this.newPathname,
  115. meta: {
  116. user_id: this.user_id,
  117. ts: new Date(),
  118. },
  119. version: this.version,
  120. projectHistoryId: this.projectHistoryId,
  121. file: this.file_id,
  122. }
  123. return this.ProjectHistoryRedisManager.queueOps
  124. .calledWithExactly(
  125. this.project_id,
  126. JSON.stringify(update),
  127. this.callback
  128. )
  129. .should.equal(true)
  130. })
  131. })
  132. return describe('queueAddEntity', function () {
  133. beforeEach(function () {
  134. this.rclient.rpush = sinon.stub().yields()
  135. this.doc_id = 1234
  136. this.rawUpdate = {
  137. pathname: (this.pathname = '/old'),
  138. docLines: (this.docLines = 'a\nb'),
  139. version: (this.version = 2),
  140. url: (this.url = 'filestore.example.com'),
  141. }
  142. this.ProjectHistoryRedisManager.queueOps = sinon.stub()
  143. return this.ProjectHistoryRedisManager.queueAddEntity(
  144. this.project_id,
  145. this.projectHistoryId,
  146. 'doc',
  147. this.doc_id,
  148. this.user_id,
  149. this.rawUpdate,
  150. this.callback
  151. )
  152. })
  153. it('should queue an update', function () {
  154. const update = {
  155. pathname: this.pathname,
  156. docLines: this.docLines,
  157. url: this.url,
  158. meta: {
  159. user_id: this.user_id,
  160. ts: new Date(),
  161. },
  162. version: this.version,
  163. projectHistoryId: this.projectHistoryId,
  164. doc: this.doc_id,
  165. }
  166. return this.ProjectHistoryRedisManager.queueOps
  167. .calledWithExactly(
  168. this.project_id,
  169. JSON.stringify(update),
  170. this.callback
  171. )
  172. .should.equal(true)
  173. })
  174. describe('queueResyncProjectStructure', function () {
  175. return it('should queue an update', function () {})
  176. })
  177. describe('queueResyncDocContent', function () {
  178. beforeEach(function () {
  179. this.doc_id = 1234
  180. this.lines = ['one', 'two']
  181. this.version = 2
  182. this.pathname = '/path'
  183. this.update = {
  184. resyncDocContent: {
  185. content: this.lines.join('\n'),
  186. version: this.version,
  187. },
  188. projectHistoryId: this.projectHistoryId,
  189. path: this.pathname,
  190. doc: this.doc_id,
  191. meta: {
  192. ts: new Date(),
  193. },
  194. }
  195. this.ProjectHistoryRedisManager.queueOps = sinon.stub()
  196. })
  197. describe('with a good doc', function () {
  198. beforeEach(function () {
  199. this.ProjectHistoryRedisManager.queueResyncDocContent(
  200. this.project_id,
  201. this.projectHistoryId,
  202. this.doc_id,
  203. this.lines,
  204. this.version,
  205. this.pathname,
  206. this.callback
  207. )
  208. })
  209. it('should check if the doc is too large', function () {
  210. this.Limits.docIsTooLarge
  211. .calledWith(
  212. JSON.stringify(this.update).length,
  213. this.lines,
  214. this.settings.max_doc_length
  215. )
  216. .should.equal(true)
  217. })
  218. it('should queue an update', function () {
  219. this.ProjectHistoryRedisManager.queueOps
  220. .calledWithExactly(
  221. this.project_id,
  222. JSON.stringify(this.update),
  223. this.callback
  224. )
  225. .should.equal(true)
  226. })
  227. })
  228. describe('with a doc that is too large', function () {
  229. beforeEach(function () {
  230. this.Limits.docIsTooLarge.returns(true)
  231. this.ProjectHistoryRedisManager.queueResyncDocContent(
  232. this.project_id,
  233. this.projectHistoryId,
  234. this.doc_id,
  235. this.lines,
  236. this.version,
  237. this.pathname,
  238. this.callback
  239. )
  240. })
  241. it('should not queue an update if the doc is too large', function () {
  242. this.ProjectHistoryRedisManager.queueOps.called.should.equal(false)
  243. this.callback
  244. .calledWith(sinon.match.instanceOf(Error))
  245. .should.equal(true)
  246. })
  247. })
  248. })
  249. })
  250. })