LockManagerTests.js 8.5 KB

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