1
0
mirror of synced 2025-12-23 21:07:12 -05:00

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:
James M. Greene
2021-03-29 12:34:22 -05:00
committed by GitHub
parent c1e3348f2e
commit 84547e54c7
7 changed files with 97 additions and 229 deletions

View File

@@ -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')
})
})
})