695 lines
No EOL
20 KiB
TypeScript
695 lines
No EOL
20 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
import { RedisCache } from '../src/redis-cache';
|
|
import type { CacheOptions } from '../src/types';
|
|
|
|
// Mock Redis instance
|
|
const createMockRedis = () => ({
|
|
status: 'ready',
|
|
on: mock(() => {}),
|
|
once: mock(() => {}),
|
|
get: mock(async () => null),
|
|
set: mock(async () => 'OK'),
|
|
setex: mock(async () => 'OK'),
|
|
del: mock(async () => 1),
|
|
exists: mock(async () => 0),
|
|
keys: mock(async () => []),
|
|
ping: mock(async () => 'PONG'),
|
|
ttl: mock(async () => -2),
|
|
eval: mock(async () => [null, -2]),
|
|
_eventCallbacks: {} as Record<string, Function>,
|
|
_triggerEvent(event: string, ...args: any[]) {
|
|
if (this._eventCallbacks[event]) {
|
|
this._eventCallbacks[event](...args);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Create mock instance getter that we can control
|
|
let mockConnectionManagerInstance: any = null;
|
|
|
|
// Mock the connection manager
|
|
mock.module('../src/connection-manager', () => ({
|
|
RedisConnectionManager: {
|
|
getInstance: () => mockConnectionManagerInstance
|
|
}
|
|
}));
|
|
|
|
describe('RedisCache', () => {
|
|
let cache: RedisCache;
|
|
let mockRedis: ReturnType<typeof createMockRedis>;
|
|
let mockLogger: any;
|
|
let mockConnectionManager: any;
|
|
|
|
beforeEach(() => {
|
|
mockLogger = {
|
|
info: mock(() => {}),
|
|
error: mock(() => {}),
|
|
warn: mock(() => {}),
|
|
debug: mock(() => {}),
|
|
};
|
|
|
|
mockRedis = createMockRedis();
|
|
mockConnectionManager = {
|
|
getConnection: mock(() => mockRedis)
|
|
};
|
|
|
|
// Set the mock instance for the module
|
|
mockConnectionManagerInstance = mockConnectionManager;
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clear mocks
|
|
mockLogger.info.mockClear();
|
|
mockLogger.error.mockClear();
|
|
mockLogger.warn.mockClear();
|
|
mockLogger.debug.mockClear();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should create cache with default options', () => {
|
|
const options: CacheOptions = {
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
};
|
|
|
|
cache = new RedisCache(options);
|
|
|
|
expect(mockConnectionManager.getConnection).toHaveBeenCalledWith({
|
|
name: 'CACHE-SERVICE',
|
|
singleton: true,
|
|
redisConfig: options.redisConfig,
|
|
logger: expect.any(Object),
|
|
});
|
|
});
|
|
|
|
it('should use custom name and prefix', () => {
|
|
const options: CacheOptions = {
|
|
name: 'MyCache',
|
|
keyPrefix: 'custom:',
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
logger: mockLogger,
|
|
};
|
|
|
|
cache = new RedisCache(options);
|
|
|
|
expect(mockConnectionManager.getConnection).toHaveBeenCalledWith({
|
|
name: 'MyCache-SERVICE',
|
|
singleton: true,
|
|
redisConfig: options.redisConfig,
|
|
logger: mockLogger,
|
|
});
|
|
});
|
|
|
|
it('should handle non-shared connections', () => {
|
|
const options: CacheOptions = {
|
|
shared: false,
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
logger: mockLogger,
|
|
};
|
|
|
|
cache = new RedisCache(options);
|
|
|
|
// Should create a new connection for non-shared
|
|
expect(mockConnectionManager.getConnection).toHaveBeenCalledWith({
|
|
name: 'CACHE-SERVICE',
|
|
singleton: false,
|
|
redisConfig: options.redisConfig,
|
|
logger: mockLogger,
|
|
});
|
|
});
|
|
|
|
it('should sanitize prefix for connection name', () => {
|
|
const options: CacheOptions = {
|
|
keyPrefix: 'my-special:prefix!',
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
};
|
|
|
|
cache = new RedisCache(options);
|
|
|
|
expect(mockConnectionManager.getConnection).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
name: 'MYSPECIALPREFIX-SERVICE',
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('get', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
keyPrefix: 'test:',
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
logger: mockLogger,
|
|
});
|
|
});
|
|
|
|
it('should get value with prefix', async () => {
|
|
const testValue = { data: 'test' };
|
|
(mockRedis.get as any).mockResolvedValue(JSON.stringify(testValue));
|
|
|
|
const result = await cache.get('mykey');
|
|
|
|
expect(mockRedis.get).toHaveBeenCalledWith('test:mykey');
|
|
expect(result).toEqual(testValue);
|
|
expect(mockLogger.debug).toHaveBeenCalledWith('Cache hit', { key: 'mykey' });
|
|
});
|
|
|
|
it('should handle cache miss', async () => {
|
|
(mockRedis.get as any).mockResolvedValue(null);
|
|
|
|
const result = await cache.get('nonexistent');
|
|
|
|
expect(result).toBeNull();
|
|
expect(mockLogger.debug).toHaveBeenCalledWith('Cache miss', { key: 'nonexistent' });
|
|
});
|
|
|
|
it('should handle non-JSON strings', async () => {
|
|
(mockRedis.get as any).mockResolvedValue('plain string');
|
|
|
|
const result = await cache.get<string>('stringkey');
|
|
|
|
expect(result).toBe('plain string');
|
|
});
|
|
|
|
it('should handle Redis errors gracefully', async () => {
|
|
(mockRedis.get as any).mockRejectedValue(new Error('Redis error'));
|
|
|
|
const result = await cache.get('errorkey');
|
|
|
|
expect(result).toBeNull();
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
'Redis get failed',
|
|
expect.objectContaining({ error: 'Redis error' })
|
|
);
|
|
});
|
|
|
|
it('should handle not ready state', async () => {
|
|
mockRedis.status = 'connecting';
|
|
|
|
const result = await cache.get('key');
|
|
|
|
expect(result).toBeNull();
|
|
expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
'Redis not ready for get, using fallback'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('set', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
keyPrefix: 'test:',
|
|
ttl: 7200,
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
logger: mockLogger,
|
|
});
|
|
});
|
|
|
|
it('should set value with default TTL', async () => {
|
|
const value = { data: 'test' };
|
|
|
|
await cache.set('mykey', value);
|
|
|
|
expect(mockRedis.setex).toHaveBeenCalledWith(
|
|
'test:mykey',
|
|
7200,
|
|
JSON.stringify(value)
|
|
);
|
|
});
|
|
|
|
it('should set value with custom TTL as number', async () => {
|
|
await cache.set('mykey', 'value', 3600);
|
|
|
|
expect(mockRedis.setex).toHaveBeenCalledWith('test:mykey', 3600, 'value');
|
|
});
|
|
|
|
it('should set value with options object', async () => {
|
|
await cache.set('mykey', 'value', { ttl: 1800 });
|
|
|
|
expect(mockRedis.setex).toHaveBeenCalledWith('test:mykey', 1800, 'value');
|
|
});
|
|
|
|
it('should handle preserveTTL option', async () => {
|
|
// Key exists with TTL
|
|
(mockRedis.ttl as any).mockResolvedValue(3600);
|
|
|
|
await cache.set('mykey', 'newvalue', { preserveTTL: true });
|
|
|
|
expect(mockRedis.ttl).toHaveBeenCalledWith('test:mykey');
|
|
expect(mockRedis.setex).toHaveBeenCalledWith('test:mykey', 3600, 'newvalue');
|
|
});
|
|
|
|
it('should handle preserveTTL with no expiry', async () => {
|
|
// Key exists with no expiry
|
|
(mockRedis.ttl as any).mockResolvedValue(-1);
|
|
|
|
await cache.set('mykey', 'value', { preserveTTL: true });
|
|
|
|
expect(mockRedis.set).toHaveBeenCalledWith('test:mykey', 'value');
|
|
});
|
|
|
|
it('should handle onlyIfExists option', async () => {
|
|
(mockRedis.set as any).mockResolvedValue(null);
|
|
|
|
await cache.set('mykey', 'value', { onlyIfExists: true });
|
|
|
|
expect(mockRedis.set).toHaveBeenCalledWith(
|
|
'test:mykey',
|
|
'value',
|
|
'EX',
|
|
7200,
|
|
'XX'
|
|
);
|
|
});
|
|
|
|
it('should handle onlyIfNotExists option', async () => {
|
|
(mockRedis.set as any).mockResolvedValue('OK');
|
|
|
|
await cache.set('mykey', 'value', { onlyIfNotExists: true });
|
|
|
|
expect(mockRedis.set).toHaveBeenCalledWith(
|
|
'test:mykey',
|
|
'value',
|
|
'EX',
|
|
7200,
|
|
'NX'
|
|
);
|
|
});
|
|
|
|
it('should get old value when requested', async () => {
|
|
const oldValue = { old: 'data' };
|
|
(mockRedis.get as any).mockResolvedValue(JSON.stringify(oldValue));
|
|
|
|
const result = await cache.set('mykey', 'newvalue', { getOldValue: true });
|
|
|
|
expect(mockRedis.get).toHaveBeenCalledWith('test:mykey');
|
|
expect(result).toEqual(oldValue);
|
|
});
|
|
|
|
it('should throw error for conflicting options', async () => {
|
|
await expect(
|
|
cache.set('mykey', 'value', { onlyIfExists: true, onlyIfNotExists: true })
|
|
).rejects.toThrow('Cannot specify both onlyIfExists and onlyIfNotExists');
|
|
});
|
|
|
|
it('should handle string values directly', async () => {
|
|
await cache.set('mykey', 'plain string');
|
|
|
|
expect(mockRedis.setex).toHaveBeenCalledWith('test:mykey', 7200, 'plain string');
|
|
});
|
|
});
|
|
|
|
describe('del', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
keyPrefix: 'test:',
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
logger: mockLogger,
|
|
});
|
|
});
|
|
|
|
it('should delete key with prefix', async () => {
|
|
await cache.del('mykey');
|
|
|
|
expect(mockRedis.del).toHaveBeenCalledWith('test:mykey');
|
|
expect(mockLogger.debug).toHaveBeenCalledWith('Cache delete', { key: 'mykey' });
|
|
});
|
|
|
|
it('should handle delete errors gracefully', async () => {
|
|
(mockRedis.del as any).mockRejectedValue(new Error('Delete failed'));
|
|
|
|
await cache.del('errorkey');
|
|
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
'Redis del failed',
|
|
expect.objectContaining({ error: 'Delete failed' })
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('exists', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
keyPrefix: 'test:',
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
});
|
|
});
|
|
|
|
it('should check key existence', async () => {
|
|
(mockRedis.exists as any).mockResolvedValue(1);
|
|
|
|
const result = await cache.exists('mykey');
|
|
|
|
expect(mockRedis.exists).toHaveBeenCalledWith('test:mykey');
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should return false for non-existent key', async () => {
|
|
(mockRedis.exists as any).mockResolvedValue(0);
|
|
|
|
const result = await cache.exists('nonexistent');
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('clear', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
keyPrefix: 'test:',
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
logger: mockLogger,
|
|
});
|
|
});
|
|
|
|
it('should clear all prefixed keys', async () => {
|
|
const keys = ['test:key1', 'test:key2', 'test:key3'];
|
|
(mockRedis.keys as any).mockResolvedValue(keys);
|
|
|
|
await cache.clear();
|
|
|
|
expect(mockRedis.keys).toHaveBeenCalledWith('test:*');
|
|
expect(mockRedis.del).toHaveBeenCalledWith(...keys);
|
|
expect(mockLogger.warn).toHaveBeenCalledWith('Cache cleared', { keysDeleted: 3 });
|
|
});
|
|
|
|
it('should handle empty cache', async () => {
|
|
(mockRedis.keys as any).mockResolvedValue([]);
|
|
|
|
await cache.clear();
|
|
|
|
expect(mockRedis.del).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('getRaw', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
keyPrefix: 'test:',
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
logger: mockLogger,
|
|
});
|
|
});
|
|
|
|
it('should get value without prefix', async () => {
|
|
const value = { raw: 'data' };
|
|
(mockRedis.get as any).mockResolvedValue(JSON.stringify(value));
|
|
|
|
const result = await cache.getRaw('raw:key');
|
|
|
|
expect(mockRedis.get).toHaveBeenCalledWith('raw:key');
|
|
expect(result).toEqual(value);
|
|
});
|
|
|
|
it('should handle parse errors', async () => {
|
|
(mockRedis.get as any).mockResolvedValue('invalid json');
|
|
|
|
const result = await cache.getRaw('badkey');
|
|
|
|
expect(result).toBe('invalid json');
|
|
expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
'Cache getRaw JSON parse failed',
|
|
expect.objectContaining({
|
|
key: 'badkey',
|
|
valueLength: 12,
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('keys', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
keyPrefix: 'test:',
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
});
|
|
});
|
|
|
|
it('should get keys with pattern and strip prefix', async () => {
|
|
(mockRedis.keys as any).mockResolvedValue([
|
|
'test:user:1',
|
|
'test:user:2',
|
|
'test:user:3',
|
|
]);
|
|
|
|
const keys = await cache.keys('user:*');
|
|
|
|
expect(mockRedis.keys).toHaveBeenCalledWith('test:user:*');
|
|
expect(keys).toEqual(['user:1', 'user:2', 'user:3']);
|
|
});
|
|
});
|
|
|
|
describe('health', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
logger: mockLogger,
|
|
});
|
|
});
|
|
|
|
it('should return true when healthy', async () => {
|
|
const result = await cache.health();
|
|
|
|
expect(mockRedis.ping).toHaveBeenCalled();
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should return false on ping failure', async () => {
|
|
(mockRedis.ping as any).mockRejectedValue(new Error('Ping failed'));
|
|
|
|
const result = await cache.health();
|
|
|
|
expect(result).toBe(false);
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
'Redis health check failed',
|
|
expect.objectContaining({ error: 'Ping failed' })
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('stats', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
enableMetrics: true,
|
|
});
|
|
});
|
|
|
|
it('should track cache hits', async () => {
|
|
(mockRedis.get as any).mockResolvedValue('value');
|
|
|
|
await cache.get('key1');
|
|
await cache.get('key2');
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.hits).toBe(2);
|
|
expect(stats.total).toBe(2);
|
|
expect(stats.hitRate).toBe(1.0);
|
|
});
|
|
|
|
it('should track cache misses', async () => {
|
|
(mockRedis.get as any).mockResolvedValue(null);
|
|
|
|
await cache.get('key1');
|
|
await cache.get('key2');
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.misses).toBe(2);
|
|
expect(stats.total).toBe(2);
|
|
expect(stats.hitRate).toBe(0);
|
|
});
|
|
|
|
it('should track errors', async () => {
|
|
mockRedis.status = 'connecting';
|
|
|
|
await cache.get('key1');
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.errors).toBe(1);
|
|
});
|
|
|
|
it('should not track stats when disabled', async () => {
|
|
cache = new RedisCache({
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
enableMetrics: false,
|
|
});
|
|
|
|
(mockRedis.get as any).mockResolvedValue('value');
|
|
await cache.get('key');
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.hits).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('waitForReady', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
});
|
|
});
|
|
|
|
it('should resolve immediately if ready', async () => {
|
|
mockRedis.status = 'ready';
|
|
|
|
await expect(cache.waitForReady(1000)).resolves.toBeUndefined();
|
|
});
|
|
|
|
it('should wait for ready event', async () => {
|
|
mockRedis.status = 'connecting';
|
|
mockRedis.once = mock((event: string, handler: Function) => {
|
|
if (event === 'ready') {
|
|
setTimeout(() => handler(), 10);
|
|
}
|
|
});
|
|
|
|
await expect(cache.waitForReady(1000)).resolves.toBeUndefined();
|
|
});
|
|
|
|
it('should timeout if not ready', async () => {
|
|
mockRedis.status = 'connecting';
|
|
mockRedis.once = mock(() => {}); // Don't trigger any events
|
|
|
|
await expect(cache.waitForReady(100)).rejects.toThrow(
|
|
'Redis connection timeout after 100ms'
|
|
);
|
|
});
|
|
|
|
it('should reject on error', async () => {
|
|
mockRedis.status = 'connecting';
|
|
mockRedis.once = mock((event: string, handler: Function) => {
|
|
if (event === 'error') {
|
|
setTimeout(() => handler(new Error('Connection failed')), 10);
|
|
}
|
|
});
|
|
|
|
await expect(cache.waitForReady(1000)).rejects.toThrow('Connection failed');
|
|
});
|
|
});
|
|
|
|
describe('isReady', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
});
|
|
});
|
|
|
|
it('should return true when ready', () => {
|
|
mockRedis.status = 'ready';
|
|
expect(cache.isReady()).toBe(true);
|
|
});
|
|
|
|
it('should return false when not ready', () => {
|
|
mockRedis.status = 'connecting';
|
|
expect(cache.isReady()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('convenience methods', () => {
|
|
beforeEach(() => {
|
|
cache = new RedisCache({
|
|
keyPrefix: 'test:',
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
});
|
|
});
|
|
|
|
it('should update value preserving TTL', async () => {
|
|
(mockRedis.ttl as any).mockResolvedValue(3600);
|
|
(mockRedis.get as any).mockResolvedValue(JSON.stringify({ old: 'value' }));
|
|
|
|
const result = await cache.update('key', { new: 'value' });
|
|
|
|
expect(mockRedis.setex).toHaveBeenCalledWith(
|
|
'test:key',
|
|
3600,
|
|
JSON.stringify({ new: 'value' })
|
|
);
|
|
expect(result).toEqual({ old: 'value' });
|
|
});
|
|
|
|
it('should setIfExists', async () => {
|
|
(mockRedis.set as any).mockResolvedValue('OK');
|
|
(mockRedis.exists as any).mockResolvedValue(1);
|
|
|
|
const result = await cache.setIfExists('key', 'value', 1800);
|
|
|
|
expect(mockRedis.set).toHaveBeenCalledWith('test:key', 'value', 'EX', 1800, 'XX');
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should setIfNotExists', async () => {
|
|
(mockRedis.set as any).mockResolvedValue('OK');
|
|
|
|
const result = await cache.setIfNotExists('key', 'value', 1800);
|
|
|
|
expect(mockRedis.set).toHaveBeenCalledWith('test:key', 'value', 'EX', 1800, 'NX');
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should replace existing value', async () => {
|
|
(mockRedis.get as any).mockResolvedValue(JSON.stringify({ old: 'data' }));
|
|
(mockRedis.set as any).mockResolvedValue('OK');
|
|
|
|
const result = await cache.replace('key', { new: 'data' }, 3600);
|
|
|
|
expect(result).toEqual({ old: 'data' });
|
|
});
|
|
|
|
it('should update field atomically', async () => {
|
|
(mockRedis.eval as any).mockResolvedValue(['{"count": 5}', 3600]);
|
|
|
|
const updater = (current: any) => ({
|
|
...current,
|
|
count: (current?.count || 0) + 1,
|
|
});
|
|
|
|
const result = await cache.updateField('key', updater);
|
|
|
|
expect(mockRedis.eval).toHaveBeenCalled();
|
|
expect(result).toEqual({ count: 5 });
|
|
});
|
|
|
|
it('should handle updateField with new key', async () => {
|
|
(mockRedis.eval as any).mockResolvedValue([null, -2]);
|
|
|
|
const updater = (_current: unknown) => ({ value: 'new' });
|
|
|
|
await cache.updateField('key', updater);
|
|
|
|
expect(mockRedis.setex).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('event handlers', () => {
|
|
it('should handle connection events for non-shared cache', () => {
|
|
// Create non-shared cache
|
|
mockRedis.on = mock((event: string, handler: Function) => {
|
|
mockRedis._eventCallbacks[event] = handler;
|
|
});
|
|
|
|
cache = new RedisCache({
|
|
shared: false,
|
|
redisConfig: { host: 'localhost', port: 6379 },
|
|
logger: mockLogger,
|
|
});
|
|
|
|
// Trigger events
|
|
mockRedis._triggerEvent('connect');
|
|
expect(mockLogger.info).toHaveBeenCalledWith('Redis cache connected');
|
|
|
|
mockRedis._triggerEvent('ready');
|
|
expect(mockLogger.info).toHaveBeenCalledWith('Redis cache ready');
|
|
|
|
mockRedis._triggerEvent('error', new Error('Test error'));
|
|
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
'Redis cache connection error',
|
|
expect.objectContaining({ error: 'Test error' })
|
|
);
|
|
|
|
mockRedis._triggerEvent('close');
|
|
expect(mockLogger.warn).toHaveBeenCalledWith('Redis cache connection closed');
|
|
|
|
mockRedis._triggerEvent('reconnecting');
|
|
expect(mockLogger.warn).toHaveBeenCalledWith('Redis cache reconnecting...');
|
|
});
|
|
});
|
|
}); |