Skip to content

Commit 9e71f5c

Browse files
authored
🐛 Fixed crash in Redis adapter when storeConfig is not provided
no-issue When storeConfig was omitted from the Redis cache adapter config, the retryStrategy callback would throw a TypeError whenever ioredis tried to reconnect to Redis, crashing Ghost instead of recovering gracefully. Fixed by using optional chaining on config.storeConfig.
1 parent 5bb46e4 commit 9e71f5c

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

ghost/core/core/server/adapters/lib/redis/AdapterCacheRedis.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class AdapterCacheRedis extends BaseCacheAdapter {
1616
* @param {Object} [config.cache] - caching instance compatible with cache-manager's redis store
1717
* @param {string} [config.host] - redis host used in case no cache instance provided
1818
* @param {number} [config.port] - redis port used in case no cache instance provided
19+
* @param {string} [config.username] - redis username used in case no cache instance provided
1920
* @param {string} [config.password] - redis password used in case no cache instance provided
2021
* @param {Object} [config.clusterConfig] - redis cluster config used in case no cache instance provided
2122
* @param {Object} [config.storeConfig] - extra redis client config used in case no cache instance provided
@@ -50,7 +51,7 @@ class AdapterCacheRedis extends BaseCacheAdapter {
5051
username: config.username,
5152
password: config.password,
5253
retryStrategy: () => {
53-
return (config.storeConfig.retryConnectSeconds || 10) * 1000;
54+
return (config.storeConfig?.retryConnectSeconds || 10) * 1000;
5455
},
5556
...config.storeConfig,
5657
clusterConfig: config.clusterConfig

ghost/core/test/unit/server/adapters/lib/redis/adapter-cache-redis.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,40 @@ describe('Adapter Cache Redis', function () {
7474
assert.equal(cache.redisClient.options.retryStrategy, false);
7575
});
7676

77+
describe('retryStrategy', function () {
78+
it('does not throw and defaults to 10 seconds when storeConfig is not provided', function () {
79+
const cache = new RedisCache({
80+
reuseConnection: false
81+
});
82+
// retryStrategy is invoked by ioredis whenever Redis becomes unavailable.
83+
// It must not crash even when storeConfig is omitted from the adapter config.
84+
assert.doesNotThrow(() => cache.redisClient.options.retryStrategy(1));
85+
assert.equal(cache.redisClient.options.retryStrategy(1), 10000);
86+
cache.redisClient.disconnect();
87+
});
88+
89+
it('defaults to 10 seconds when retryConnectSeconds is not set in storeConfig', function () {
90+
const cache = new RedisCache({
91+
storeConfig: {
92+
lazyConnect: true
93+
},
94+
reuseConnection: false
95+
});
96+
assert.equal(cache.redisClient.options.retryStrategy(1), 10000);
97+
});
98+
99+
it('uses retryConnectSeconds from storeConfig when provided', function () {
100+
const cache = new RedisCache({
101+
storeConfig: {
102+
lazyConnect: true,
103+
retryConnectSeconds: 5
104+
},
105+
reuseConnection: false
106+
});
107+
assert.equal(cache.redisClient.options.retryStrategy(1), 5000);
108+
});
109+
});
110+
77111
describe('get', function () {
78112
it('can get a value from the cache', async function () {
79113
const cacheStub = createCacheStub();

0 commit comments

Comments
 (0)