LockManagerTests.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /* eslint-disable
  2. mocha/no-nested-tests,
  3. no-return-assign,
  4. no-undef,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS101: Remove unnecessary use of Array.from
  12. * DS102: Remove unnecessary code created because of implicit returns
  13. * DS207: Consider shorter variations of null checks
  14. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  15. */
  16. const sinon = require('sinon')
  17. const { expect } = require('chai')
  18. const modulePath = '../../../../app/js/LockManager.js'
  19. const SandboxedModule = require('sandboxed-module')
  20. describe('LockManager', function () {
  21. beforeEach(function () {
  22. this.Settings = {
  23. redis: {
  24. lock: {},
  25. },
  26. }
  27. this.LockManager = SandboxedModule.require(modulePath, {
  28. requires: {
  29. '@overleaf/redis-wrapper': {
  30. createClient: () => {
  31. return (this.rclient = { auth: sinon.stub() })
  32. },
  33. },
  34. '@overleaf/settings': this.Settings,
  35. },
  36. })
  37. this.key = 'lock-key'
  38. return (this.callback = sinon.stub())
  39. })
  40. describe('checkLock', function () {
  41. describe('when the lock is taken', function () {
  42. beforeEach(function () {
  43. this.rclient.exists = sinon.stub().callsArgWith(1, null, '1')
  44. return this.LockManager.checkLock(this.key, this.callback)
  45. })
  46. it('should check the lock in redis', function () {
  47. return this.rclient.exists.calledWith(this.key).should.equal(true)
  48. })
  49. return it('should return the callback with false', function () {
  50. return this.callback.calledWith(null, false).should.equal(true)
  51. })
  52. })
  53. return describe('when the lock is free', function () {
  54. beforeEach(function () {
  55. this.rclient.exists = sinon.stub().callsArgWith(1, null, '0')
  56. return this.LockManager.checkLock(this.key, this.callback)
  57. })
  58. return it('should return the callback with true', function () {
  59. return this.callback.calledWith(null, true).should.equal(true)
  60. })
  61. })
  62. })
  63. describe('tryLock', function () {
  64. describe('when the lock is taken', function () {
  65. beforeEach(function () {
  66. this.rclient.set = sinon.stub().callsArgWith(5, null, null)
  67. this.LockManager.randomLock = sinon
  68. .stub()
  69. .returns('locked-random-value')
  70. return this.LockManager.tryLock(this.key, this.callback)
  71. })
  72. it('should check the lock in redis', function () {
  73. return this.rclient.set
  74. .calledWith(
  75. this.key,
  76. 'locked-random-value',
  77. 'EX',
  78. this.LockManager.LOCK_TTL,
  79. 'NX'
  80. )
  81. .should.equal(true)
  82. })
  83. return it('should return the callback with false', function () {
  84. return this.callback.calledWith(null, false).should.equal(true)
  85. })
  86. })
  87. return describe('when the lock is free', function () {
  88. beforeEach(function () {
  89. this.rclient.set = sinon.stub().callsArgWith(5, null, 'OK')
  90. return this.LockManager.tryLock(this.key, this.callback)
  91. })
  92. return it('should return the callback with true', function () {
  93. return this.callback.calledWith(null, true).should.equal(true)
  94. })
  95. })
  96. })
  97. describe('deleteLock', function () {
  98. return beforeEach(function () {
  99. beforeEach(function () {
  100. this.rclient.del = sinon.stub().callsArg(1)
  101. return this.LockManager.deleteLock(this.key, this.callback)
  102. })
  103. it('should delete the lock in redis', function () {
  104. return this.rclient.del.calledWith(key).should.equal(true)
  105. })
  106. return it('should call the callback', function () {
  107. return this.callback.called.should.equal(true)
  108. })
  109. })
  110. })
  111. describe('getLock', function () {
  112. describe('when the lock is not taken', function () {
  113. beforeEach(function (done) {
  114. this.LockManager.tryLock = sinon.stub().callsArgWith(1, null, true)
  115. return this.LockManager.getLock(this.key, (...args) => {
  116. this.callback(...Array.from(args || []))
  117. return done()
  118. })
  119. })
  120. it('should try to get the lock', function () {
  121. return this.LockManager.tryLock.calledWith(this.key).should.equal(true)
  122. })
  123. it('should only need to try once', function () {
  124. return this.LockManager.tryLock.callCount.should.equal(1)
  125. })
  126. return it('should return the callback', function () {
  127. return this.callback.calledWith(null).should.equal(true)
  128. })
  129. })
  130. describe('when the lock is initially set', function () {
  131. beforeEach(function (done) {
  132. const startTime = Date.now()
  133. this.LockManager.LOCK_TEST_INTERVAL = 5
  134. this.LockManager.tryLock = function (docId, callback) {
  135. if (callback == null) {
  136. callback = function () {}
  137. }
  138. if (Date.now() - startTime < 100) {
  139. return callback(null, false)
  140. } else {
  141. return callback(null, true)
  142. }
  143. }
  144. sinon.spy(this.LockManager, 'tryLock')
  145. return this.LockManager.getLock(this.key, (...args) => {
  146. this.callback(...Array.from(args || []))
  147. return done()
  148. })
  149. })
  150. it('should call tryLock multiple times until free', function () {
  151. return (this.LockManager.tryLock.callCount > 1).should.equal(true)
  152. })
  153. return it('should return the callback', function () {
  154. return this.callback.calledWith(null).should.equal(true)
  155. })
  156. })
  157. return describe('when the lock times out', function () {
  158. beforeEach(function (done) {
  159. const time = Date.now()
  160. this.LockManager.MAX_LOCK_WAIT_TIME = 5
  161. this.LockManager.tryLock = sinon.stub().callsArgWith(1, null, false)
  162. return this.LockManager.getLock(this.key, (...args) => {
  163. this.callback(...Array.from(args || []))
  164. return done()
  165. })
  166. })
  167. return it('should return the callback with an error', function () {
  168. return this.callback
  169. .calledWith(sinon.match.instanceOf(Error))
  170. .should.equal(true)
  171. })
  172. })
  173. })
  174. return describe('runWithLock', function () {
  175. describe('with successful run', function () {
  176. beforeEach(function () {
  177. this.runner = function (releaseLock) {
  178. if (releaseLock == null) {
  179. releaseLock = function () {}
  180. }
  181. return releaseLock()
  182. }
  183. sinon.spy(this, 'runner')
  184. this.LockManager.getLock = sinon.stub().callsArg(1)
  185. this.LockManager.releaseLock = sinon.stub().callsArg(2)
  186. return this.LockManager.runWithLock(
  187. this.key,
  188. this.runner,
  189. this.callback
  190. )
  191. })
  192. it('should get the lock', function () {
  193. return this.LockManager.getLock.calledWith(this.key).should.equal(true)
  194. })
  195. it('should run the passed function', function () {
  196. return this.runner.called.should.equal(true)
  197. })
  198. it('should release the lock', function () {
  199. return this.LockManager.releaseLock
  200. .calledWith(this.key)
  201. .should.equal(true)
  202. })
  203. return it('should call the callback', function () {
  204. return this.callback.called.should.equal(true)
  205. })
  206. })
  207. describe('when the runner function returns an error', function () {
  208. beforeEach(function () {
  209. this.error = new Error('oops')
  210. this.runner = releaseLock => {
  211. if (releaseLock == null) {
  212. releaseLock = function () {}
  213. }
  214. return releaseLock(this.error)
  215. }
  216. sinon.spy(this, 'runner')
  217. this.LockManager.getLock = sinon.stub().callsArg(1)
  218. this.LockManager.releaseLock = sinon.stub().callsArg(2)
  219. return this.LockManager.runWithLock(
  220. this.key,
  221. this.runner,
  222. this.callback
  223. )
  224. })
  225. it('should release the lock', function () {
  226. return this.LockManager.releaseLock
  227. .calledWith(this.key)
  228. .should.equal(true)
  229. })
  230. return it('should call the callback with the error', function () {
  231. return this.callback.calledWith(this.error).should.equal(true)
  232. })
  233. })
  234. return describe('releaseLock', function () {
  235. describe('when the lock is current', function () {
  236. beforeEach(function () {
  237. this.rclient.eval = sinon.stub().yields(null, 1)
  238. return this.LockManager.releaseLock(
  239. this.key,
  240. this.lockValue,
  241. this.callback
  242. )
  243. })
  244. it('should clear the data from redis', function () {
  245. return this.rclient.eval
  246. .calledWith(
  247. this.LockManager.unlockScript,
  248. 1,
  249. this.key,
  250. this.lockValue
  251. )
  252. .should.equal(true)
  253. })
  254. return it('should call the callback', function () {
  255. return this.callback.called.should.equal(true)
  256. })
  257. })
  258. return describe('when the lock has expired', function () {
  259. beforeEach(function () {
  260. this.rclient.eval = sinon.stub().yields(null, 0)
  261. return this.LockManager.releaseLock(
  262. this.key,
  263. this.lockValue,
  264. this.callback
  265. )
  266. })
  267. return it('should return an error if the lock has expired', function () {
  268. return this.callback
  269. .calledWith(
  270. sinon.match.has('message', 'tried to release timed out lock')
  271. )
  272. .should.equal(true)
  273. })
  274. })
  275. })
  276. })
  277. })