Use node-redis for page cache (#18421)
* Use [node-]redis as a direct dependency * Extract Redis client creation to its own module * Attach extensive logging in the Redis client creation module * Allow the rate limiter to pass requests when Redis is disconnected * Update rate-limit-redis * Default error input to empty object for formatRedisError method * Provide a name for the rate limiter's Redis client * Include redis-mock, exclude ioredis/ioredis-mock * Remove unused RedisAccessor#exists method * Switch RedisAccessor to use redis/redis-mock * Provide a name for logging on the Redis page cache * Remove extraneous trailing space from Redis logging prefix Our updated use of console.* will already be adding a space after the prefix * Replace ioredis-mock with redis-mock in tests * Revert removal of ioredis dependency * Bind Redis client to async promisified methods * Extract former RedisAccessor constructor tests to new create-client tests * Update RedisAccessor tests to work with the callback-based redis client * Handle formatting Redis errors (or not) with more resiliency
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
const InMemoryRedis = require('ioredis-mock')
|
||||
const { RedisClient: InMemoryRedis } = require('redis-mock')
|
||||
const RedisAccessor = require('../../lib/redis-accessor')
|
||||
|
||||
describe('RedisAccessor', () => {
|
||||
@@ -59,32 +59,6 @@ describe('RedisAccessor', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
test('throws if databaseNumber is provided but is not a number', async () => {
|
||||
expect(() => new RedisAccessor({ databaseNumber: 'dbName' })).toThrowError(
|
||||
new TypeError('Redis database number must be an integer between 0 and 15 but was: "dbName"')
|
||||
)
|
||||
})
|
||||
|
||||
test('throws if databaseNumber is provided but is not an integer', async () => {
|
||||
expect(() => new RedisAccessor({ databaseNumber: 1.5 })).toThrowError(
|
||||
new TypeError('Redis database number must be an integer between 0 and 15 but was: 1.5')
|
||||
)
|
||||
})
|
||||
|
||||
test('throws if databaseNumber is provided but is less than 0', async () => {
|
||||
expect(() => new RedisAccessor({ databaseNumber: -1 })).toThrowError(
|
||||
new TypeError('Redis database number must be an integer between 0 and 15 but was: -1')
|
||||
)
|
||||
})
|
||||
|
||||
test('throws if databaseNumber is provided but is greater than max allowed', async () => {
|
||||
expect(() => new RedisAccessor({ databaseNumber: 16 })).toThrowError(
|
||||
new TypeError('Redis database number must be an integer between 0 and 15 but was: 16')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#prefix method', () => {
|
||||
test('returns prefixed key', async () => {
|
||||
const prefix = 'myPrefix'
|
||||
@@ -203,7 +177,7 @@ describe('RedisAccessor', () => {
|
||||
|
||||
test('resolves to false if value was not set', async () => {
|
||||
const instance = new RedisAccessor()
|
||||
instance._client.set = jest.fn(async () => 'NOT_OK')
|
||||
instance._client.set = jest.fn((...args) => args.pop()(null, 'NOT_OK'))
|
||||
|
||||
expect(await instance.set('myKey', 'myValue')).toBe(false)
|
||||
})
|
||||
@@ -213,7 +187,8 @@ describe('RedisAccessor', () => {
|
||||
const setSpy = jest.spyOn(instance._client, 'set')
|
||||
|
||||
await instance.set('myKey', 'myValue')
|
||||
expect(setSpy).toBeCalledWith('myKey', 'myValue')
|
||||
expect(setSpy.mock.calls.length).toBe(1)
|
||||
expect(setSpy.mock.calls[0].slice(0, 2)).toEqual(['myKey', 'myValue'])
|
||||
})
|
||||
|
||||
test('resolves to false if Redis replies with an error and `allowSetFailures` option is set to true', async () => {
|
||||
@@ -221,7 +196,7 @@ describe('RedisAccessor', () => {
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
|
||||
|
||||
const instance = new RedisAccessor({ prefix: 'myPrefix', allowSetFailures: true })
|
||||
instance._client.set = jest.fn(async () => { throw new Error('Redis ReplyError') })
|
||||
instance._client.set = jest.fn((...args) => args.pop()(new Error('Redis ReplyError')))
|
||||
|
||||
const result = await instance.set('myKey', 'myValue')
|
||||
|
||||
@@ -241,7 +216,7 @@ Error: Redis ReplyError`
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
|
||||
|
||||
const instance = new RedisAccessor({ prefix: 'myPrefix' })
|
||||
instance._client.set = jest.fn(async () => { throw new Error('Redis ReplyError') })
|
||||
instance._client.set = jest.fn((...args) => args.pop()(new Error('Redis ReplyError')))
|
||||
|
||||
await expect(instance.set('myKey', 'myValue')).rejects.toThrowError(
|
||||
new Error(`Failed to set value in Redis.
|
||||
@@ -279,7 +254,8 @@ Error: Redis ReplyError`
|
||||
|
||||
await instance.set('myKey', 'myValue', { expireIn: 20 })
|
||||
expect(argSpy).toBeCalled()
|
||||
expect(setSpy).toBeCalledWith('myKey', 'myValue', 'PX', 20)
|
||||
expect(setSpy.mock.calls.length).toBe(1)
|
||||
expect(setSpy.mock.calls[0].slice(0, 4)).toEqual(['myKey', 'myValue', 'PX', 20])
|
||||
|
||||
argSpy.mockRestore()
|
||||
})
|
||||
@@ -304,42 +280,24 @@ Error: Redis ReplyError`
|
||||
|
||||
test('retrieves matching entry from Redis with #_client.get', async () => {
|
||||
const instance = new RedisAccessor()
|
||||
const getSpy = jest.spyOn(instance._client, 'get')
|
||||
let callbackSpy
|
||||
const originalGet = instance._client.get.bind(instance._client)
|
||||
instance._client.get = jest.fn((...args) => {
|
||||
const realCallback = args.pop()
|
||||
callbackSpy = jest.fn((error, value) => {
|
||||
realCallback(error, value)
|
||||
})
|
||||
|
||||
return originalGet(...args, callbackSpy)
|
||||
})
|
||||
|
||||
await instance.set('myKey', 'myValue')
|
||||
await instance.get('myKey')
|
||||
|
||||
expect(getSpy).toBeCalledWith('myKey')
|
||||
expect(getSpy).toHaveReturnedWith(Promise.resolve('myValue'))
|
||||
})
|
||||
})
|
||||
expect(instance._client.get.mock.calls.length).toBe(1)
|
||||
expect(instance._client.get.mock.calls[0].slice(0, 1)).toEqual(['myKey'])
|
||||
|
||||
describe('#exists method', () => {
|
||||
test('resolves to true if matching entry exists in Redis', async () => {
|
||||
const instance = new RedisAccessor()
|
||||
|
||||
await instance.set('myKey', 'myValue')
|
||||
|
||||
const result = await instance.exists('myKey')
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
test('resolves to false if no matching entry exists in Redis', async () => {
|
||||
const instance = new RedisAccessor()
|
||||
|
||||
const result = await instance.exists('fakeKey')
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test('checks for matching entry from Redis with #_client.exists', async () => {
|
||||
const instance = new RedisAccessor()
|
||||
const existsSpy = jest.spyOn(instance._client, 'exists')
|
||||
|
||||
await instance.set('myKey', 'myValue')
|
||||
await instance.exists('myKey')
|
||||
|
||||
expect(existsSpy).toBeCalledWith('myKey')
|
||||
expect(existsSpy).toHaveReturnedWith(Promise.resolve(true))
|
||||
expect(callbackSpy).toHaveBeenCalledWith(null, 'myValue')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user