test.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. /*
  2. * decaffeinate suggestions:
  3. * DS102: Remove unnecessary code created because of implicit returns
  4. * DS206: Consider reworking classes to avoid initClass
  5. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  6. */
  7. require('chai').should()
  8. const SandboxedModule = require('sandboxed-module')
  9. const assert = require('node:assert')
  10. const path = require('node:path')
  11. const sinon = require('sinon')
  12. const modulePath = path.join(__dirname, './../../../index.js')
  13. const redisLockerModulePath = path.join(__dirname, './../../../RedisLocker.js')
  14. const { expect } = require('chai')
  15. describe('index', function () {
  16. beforeEach(function () {
  17. let Cluster, IoRedis, ioredisConstructor
  18. this.settings = {}
  19. this.ioredisConstructor = ioredisConstructor = sinon.stub()
  20. this.ioredis = IoRedis = (function () {
  21. let createIoRedis
  22. IoRedis = class IoRedis {
  23. static initClass() {
  24. this.prototype.on = sinon.stub()
  25. createIoRedis = ioredisConstructor
  26. }
  27. constructor() {
  28. return createIoRedis.apply(this, arguments)
  29. }
  30. }
  31. IoRedis.initClass()
  32. return IoRedis
  33. })()
  34. this.ioredis.Cluster = Cluster = (function () {
  35. Cluster = class Cluster {
  36. static initClass() {
  37. this.prototype.on = sinon.stub()
  38. }
  39. constructor(config, options) {
  40. this.config = config
  41. this.options = options
  42. }
  43. }
  44. Cluster.initClass()
  45. return Cluster
  46. })()
  47. this.redis = SandboxedModule.require(modulePath, {
  48. requires: {
  49. ioredis: this.ioredis,
  50. },
  51. globals: {
  52. process,
  53. Buffer,
  54. },
  55. })
  56. this.auth_pass = '1234 pass'
  57. this.RedisLocker = SandboxedModule.require(redisLockerModulePath, {
  58. requires: {
  59. '@overleaf/metrics': {
  60. inc() {},
  61. },
  62. },
  63. globals: {
  64. process,
  65. Math,
  66. Buffer,
  67. },
  68. })
  69. })
  70. describe('lock TTL', function () {
  71. it('should throw an error when creating a client with wrong type', function () {
  72. const createNewRedisLock = () => {
  73. return new this.RedisLocker({
  74. lockTTLSeconds: '60',
  75. })
  76. }
  77. expect(createNewRedisLock).to.throw(
  78. 'redis lock TTL must be at least 30s and below 1000s'
  79. )
  80. })
  81. it('should throw an error when creating a client with small TTL', function () {
  82. const createNewRedisLock = () => {
  83. return new this.RedisLocker({
  84. lockTTLSeconds: 1,
  85. })
  86. }
  87. expect(createNewRedisLock).to.throw(
  88. 'redis lock TTL must be at least 30s and below 1000s'
  89. )
  90. })
  91. it('should throw an error when creating a client with huge TTL', function () {
  92. const createNewRedisLock = () => {
  93. return new this.RedisLocker({
  94. lockTTLSeconds: 30_000,
  95. })
  96. }
  97. expect(createNewRedisLock).to.throw(
  98. 'redis lock TTL must be at least 30s and below 1000s'
  99. )
  100. })
  101. })
  102. describe('redis-sentinel', function () {
  103. it('should throw an error when creating a client', function () {
  104. const redisSentinelOptions = {
  105. endpoints: ['127.0.0.1:1234', '127.0.0.1:2345', '127.0.0.1:3456'],
  106. }
  107. const createNewClient = () => {
  108. this.redis.createClient(redisSentinelOptions)
  109. }
  110. expect(createNewClient).to.throw(
  111. '@overleaf/redis-wrapper: redis-sentinel is no longer supported'
  112. )
  113. })
  114. })
  115. describe('single node redis', function () {
  116. beforeEach(function () {
  117. return (this.standardOpts = {
  118. auth_pass: this.auth_pass,
  119. port: 1234,
  120. host: 'redis.mysite.env',
  121. })
  122. })
  123. it('should work without opts', function () {
  124. this.redis.createClient()
  125. })
  126. it('should use the ioredis driver in single-instance mode if a non array is passed', function () {
  127. const client = this.redis.createClient(this.standardOpts)
  128. return assert.equal(client.constructor, this.ioredis)
  129. })
  130. return it('should call createClient for the ioredis driver in single-instance mode if a non array is passed', function () {
  131. this.redis.createClient(this.standardOpts)
  132. return this.ioredisConstructor
  133. .calledWith(sinon.match(this.standardOpts))
  134. .should.equal(true)
  135. })
  136. })
  137. describe('cluster', function () {
  138. beforeEach(function () {
  139. this.cluster = [{ mock: 'cluster' }, { mock: 'cluster2' }]
  140. this.extraOptions = { keepAlive: 100 }
  141. return (this.settings = {
  142. cluster: this.cluster,
  143. redisOptions: this.extraOptions,
  144. key_schema: {
  145. foo(x) {
  146. return `${x}`
  147. },
  148. },
  149. })
  150. })
  151. it('should pass the options correctly though with no options', function () {
  152. const client = this.redis.createClient({ cluster: this.cluster })
  153. assert(client instanceof this.ioredis.Cluster)
  154. return client.config.should.deep.equal(this.cluster)
  155. })
  156. it('should not pass the key_schema through to the driver', function () {
  157. const client = this.redis.createClient({
  158. cluster: this.cluster,
  159. key_schema: 'foobar',
  160. })
  161. assert(client instanceof this.ioredis.Cluster)
  162. client.config.should.deep.equal(this.cluster)
  163. return expect(client.options).to.deep.equal({ retry_max_delay: 5000 })
  164. })
  165. return it('should pass the options correctly though with additional options', function () {
  166. const client = this.redis.createClient(this.settings)
  167. assert(client instanceof this.ioredis.Cluster)
  168. client.config.should.deep.equal(this.cluster)
  169. // need to use expect here because of _.clone in sandbox
  170. return expect(client.options).to.deep.equal({
  171. redisOptions: this.extraOptions,
  172. retry_max_delay: 5000,
  173. })
  174. })
  175. })
  176. return describe('monkey patch ioredis exec', function () {
  177. beforeEach(function () {
  178. this.callback = sinon.stub()
  179. this.results = []
  180. this.multiOrig = { exec: sinon.stub().yields(null, this.results) }
  181. this.client = { multi: sinon.stub().returns(this.multiOrig) }
  182. this.ioredisConstructor.returns(this.client)
  183. this.redis.createClient(this.client)
  184. return (this.multi = this.client.multi())
  185. })
  186. it('should return the old redis format for an array', function () {
  187. this.results[0] = [null, 42]
  188. this.results[1] = [null, 'foo']
  189. this.multi.exec(this.callback)
  190. return this.callback.calledWith(null, [42, 'foo']).should.equal(true)
  191. })
  192. return it('should return the old redis format when there is an error', function () {
  193. this.results[0] = [null, 42]
  194. this.results[1] = ['error', 'foo']
  195. this.multi.exec(this.callback)
  196. return this.callback.calledWith('error').should.equal(true)
  197. })
  198. })
  199. })