test(core): Stop Redis retryStrategy from exiting the process under test (#31959)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matsu
2026-06-09 09:32:20 +00:00
committed by GitHub
co-authored by Claude Opus 4.8
parent cf223af11c
commit dcad5c2b90
2 changed files with 46 additions and 3 deletions
@@ -7,6 +7,14 @@ import { RedisClientService } from '@/services/redis-client.service';
type EventHandler = (...args: unknown[]) => void;
/**
* The service disables process exit under test by default. Re-enable it so the
* cumulative-timeout accounting tests can assert on the exit decision.
*/
const enableExitOnRedisUnreachable = (service: RedisClientService) => {
(service as unknown as { exitOnRedisUnreachable: boolean }).exitOnRedisUnreachable = true;
};
jest.mock('ioredis', () => {
return jest.fn().mockImplementation(() => {
return {
@@ -129,6 +137,7 @@ describe('RedisClientService', () => {
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
const service = new RedisClientService(logger, globalConfig);
enableExitOnRedisUnreachable(service);
service.createClient({ type: 'client(bull)' });
service.createClient({ type: 'subscriber(bull)' });
@@ -168,6 +177,7 @@ describe('RedisClientService', () => {
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
const service = new RedisClientService(logger, globalConfig);
enableExitOnRedisUnreachable(service);
service.createClient({ type: 'client(bull)' });
service.createClient({ type: 'subscriber(bull)' });
@@ -219,6 +229,7 @@ describe('RedisClientService', () => {
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
const service = new RedisClientService(logger, globalConfig);
enableExitOnRedisUnreachable(service);
service.createClient({ type: 'client(bull)' });
const mockClient = mockedRedis.mock.results[0].value;
@@ -247,5 +258,28 @@ describe('RedisClientService', () => {
dateNowSpy.mockRestore();
exitSpy.mockRestore();
});
it('should not exit the process under test even when the timeout is exceeded', () => {
const T0 = 1_700_000_000_000;
const dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(T0);
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
// No enableExitOnRedisUnreachable() — exercise the default test behaviour.
const service = new RedisClientService(logger, globalConfig);
service.createClient({ type: 'client(bull)' });
const retryStrategy = mockedRedis.mock.calls[0][0].retryStrategy as () => number;
// Fail continuously well past the timeout threshold.
dateNowSpy.mockReturnValue(T0 + 1_000);
retryStrategy();
dateNowSpy.mockReturnValue(T0 + 12_001);
retryStrategy();
expect(exitSpy).not.toHaveBeenCalled();
dateNowSpy.mockRestore();
exitSpy.mockRestore();
});
});
});
@@ -1,4 +1,4 @@
import { Logger } from '@n8n/backend-common';
import { inTest, Logger } from '@n8n/backend-common';
import { GlobalConfig } from '@n8n/config';
import { Debounce } from '@n8n/decorators';
import { Service } from '@n8n/di';
@@ -43,6 +43,13 @@ export class RedisClientService extends TypedEmitter<RedisEventMap> {
/** Whether any client has lost connection to Redis. */
private lostConnection = false;
/**
* Whether to exit the process when Redis stays unreachable past the timeout.
* Disabled under test so a client that can never reach Redis (e.g. no Redis
* in DB-test runs) does not call `process.exit` and kill the test worker.
*/
private readonly exitOnRedisUnreachable = !inTest;
constructor(
private readonly logger: Logger,
private readonly globalConfig: GlobalConfig,
@@ -243,8 +250,10 @@ export class RedisClientService extends TypedEmitter<RedisEventMap> {
if (cumulativeTimeout > this.config.maxTimeout) {
const maxTimeout = Math.round(this.config.maxTimeout / 1000) + 's';
this.logger.error(`Unable to connect to Redis after trying to connect for ${maxTimeout}`);
this.logger.error('Exiting process due to Redis connection error');
process.exit(1);
if (this.exitOnRedisUnreachable) {
this.logger.error('Exiting process due to Redis connection error');
process.exit(1);
}
}
}