Skip to content

Commit 938f3dc

Browse files
authored
Merge branch 'main' into ignore-images-in-meta-test
2 parents 9384171 + 22e8d75 commit 938f3dc

4 files changed

Lines changed: 85 additions & 5 deletions

File tree

lib/excluded-links.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ module.exports = [
1313
'https://haveibeenpwned.com/',
1414
'https://www.ilo.org/dyn/normlex/en/f\\?p=NORMLEXPUB:12100:0::NO::P12100_ILO_CODE:P029',
1515
'https://www.linkedin.com/company/github',
16-
'https://www.facebook.com/'
16+
'https://www.facebook.com/',
17+
'https://ko-fi.com/'
1718
]

lib/redis-accessor.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ const { CI, NODE_ENV, REDIS_URL } = process.env
88
const useRealRedis = !CI && NODE_ENV !== 'test' && !!REDIS_URL
99

1010
class RedisAccessor {
11-
constructor ({ databaseNumber = 0, prefix = null, allowSetFailures = false, name = null } = {}) {
11+
constructor ({
12+
databaseNumber = 0,
13+
prefix = null,
14+
allowSetFailures = false,
15+
allowGetFailures = false,
16+
name = null
17+
} = {}) {
1218
const redisClient = useRealRedis
1319
? createRedisClient({
1420
url: REDIS_URL,
@@ -23,6 +29,9 @@ class RedisAccessor {
2329

2430
// Allow for graceful failures if a Redis SET operation fails?
2531
this._allowSetFailures = allowSetFailures === true
32+
33+
// Allow for graceful failures if a Redis GET operation fails?
34+
this._allowGetFailures = allowGetFailures === true
2635
}
2736

2837
/** @private */
@@ -105,8 +114,24 @@ Error: ${err.message}`
105114

106115
async get (key) {
107116
const getAsync = promisify(this._client.get).bind(this._client)
108-
const value = await getAsync(this.prefix(key))
109-
return value
117+
const fullKey = this.prefix(key)
118+
119+
try {
120+
const value = await getAsync(fullKey)
121+
return value
122+
} catch (err) {
123+
const errorText = `Failed to get value from Redis.
124+
Key: ${fullKey}
125+
Error: ${err.message}`
126+
127+
if (this._allowGetFailures === true) {
128+
// Allow for graceful failure
129+
console.error(errorText)
130+
return null
131+
}
132+
133+
throw new Error(errorText)
134+
}
110135
}
111136
}
112137

middleware/render-page.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const pageCache = new RedisAccessor({
1717
prefix: (HEROKU_RELEASE_VERSION ? HEROKU_RELEASE_VERSION + ':' : '') + 'rp',
1818
// Allow for graceful failures if a Redis SET operation fails
1919
allowSetFailures: true,
20+
// Allow for graceful failures if a Redis GET operation fails
21+
allowGetFailures: true,
2022
name: 'page-cache'
2123
})
2224

tests/unit/redis-accessor.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,25 @@ describe('RedisAccessor', () => {
1111

1212
test('has expected instance properties', async () => {
1313
const instance = new RedisAccessor()
14-
expect(Object.keys(instance).sort()).toEqual(['_allowSetFailures', '_client', '_prefix'])
14+
expect(Object.keys(instance).sort()).toEqual(['_allowGetFailures', '_allowSetFailures', '_client', '_prefix'])
1515
})
1616

1717
test('has expected static methods', async () => {
1818
expect(typeof RedisAccessor.translateSetArguments).toBe('function')
1919
})
2020

21+
describe('#_allowGetFailures property', () => {
22+
test('defaults to false', async () => {
23+
const instance = new RedisAccessor()
24+
expect(instance._allowGetFailures).toBe(false)
25+
})
26+
27+
test('is expected value', async () => {
28+
const instance = new RedisAccessor({ allowGetFailures: true })
29+
expect(instance._allowGetFailures).toBe(true)
30+
})
31+
})
32+
2133
describe('#_allowSetFailures property', () => {
2234
test('defaults to false', async () => {
2335
const instance = new RedisAccessor()
@@ -299,5 +311,45 @@ Error: Redis ReplyError`
299311

300312
expect(callbackSpy).toHaveBeenCalledWith(null, 'myValue')
301313
})
314+
315+
test('resolves to null if Redis replies with an error and `allowGetFailures` option is set to true', async () => {
316+
// Temporarily override `console.error`
317+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
318+
319+
const instance = new RedisAccessor({ prefix: 'myPrefix', allowGetFailures: true })
320+
instance._client.get = jest.fn((...args) => args.pop()(new Error('Redis ReplyError')))
321+
322+
const result = await instance.get('myKey', 'myValue')
323+
324+
expect(result).toBe(null)
325+
expect(consoleErrorSpy).toBeCalledWith(
326+
`Failed to get value from Redis.
327+
Key: myPrefix:myKey
328+
Error: Redis ReplyError`
329+
)
330+
331+
// Restore `console.error`
332+
consoleErrorSpy.mockRestore()
333+
})
334+
335+
test('rejects if Redis replies with an error and `allowGetFailures` option is not set to true', async () => {
336+
// Temporarily override `console.error`
337+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
338+
339+
const instance = new RedisAccessor({ prefix: 'myPrefix' })
340+
instance._client.get = jest.fn((...args) => args.pop()(new Error('Redis ReplyError')))
341+
342+
await expect(instance.get('myKey')).rejects.toThrowError(
343+
new Error(`Failed to get value from Redis.
344+
Key: myPrefix:myKey
345+
Error: Redis ReplyError`
346+
)
347+
)
348+
349+
expect(consoleErrorSpy).not.toBeCalled()
350+
351+
// Restore `console.error`
352+
consoleErrorSpy.mockRestore()
353+
})
302354
})
303355
})

0 commit comments

Comments
 (0)