Skip to main content
Glama
redis.test.ts15.6 kB
import { redis, initializeRedis } from '../../../src/database/redis'; import { MockFactory } from '../../utils/test-helpers'; import { fixtures } from '../../utils/fixtures'; import Redis from 'ioredis'; // Mock dependencies jest.mock('ioredis'); jest.mock('../../../src/config/config', () => ({ config: { redis: { url: 'redis://localhost:6379/1', db: 1, connectTimeout: 5000, commandTimeout: 2000, }, env: 'test', }, })); jest.mock('../../../src/utils/logger', () => ({ logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(), trace: jest.fn(), child: jest.fn(() => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(), trace: jest.fn(), })), }, })); describe('Redis Database Connection', () => { let mockRedisInstance: jest.Mocked<Redis>; let mockLogger: any; beforeEach(() => { mockLogger = require('../../../src/utils/logger').logger; // Create mock Redis instance mockRedisInstance = { connect: jest.fn().mockResolvedValue(undefined), disconnect: jest.fn().mockResolvedValue(undefined), ping: jest.fn().mockResolvedValue('PONG'), get: jest.fn(), set: jest.fn(), del: jest.fn(), exists: jest.fn(), expire: jest.fn(), ttl: jest.fn(), incr: jest.fn(), setex: jest.fn(), sadd: jest.fn(), srem: jest.fn(), smembers: jest.fn(), hget: jest.fn(), hset: jest.fn(), hgetall: jest.fn(), keys: jest.fn(), on: jest.fn(), once: jest.fn(), removeListener: jest.fn(), status: 'ready', } as any; (Redis as jest.MockedClass<typeof Redis>).mockImplementation(() => mockRedisInstance); jest.clearAllMocks(); }); describe('Redis initialization', () => { it('should initialize Redis connection successfully', async () => { await initializeRedis(); expect(Redis).toHaveBeenCalledWith( 'redis://localhost:6379/1', expect.objectContaining({ db: 1, connectTimeout: 5000, maxRetriesPerRequest: 3, }) ); expect(mockRedisInstance.on).toHaveBeenCalledWith('connect', expect.any(Function)); expect(mockRedisInstance.on).toHaveBeenCalledWith('error', expect.any(Function)); expect(mockRedisInstance.on).toHaveBeenCalledWith('reconnecting', expect.any(Function)); }); it('should handle connection success events', async () => { await initializeRedis(); // Simulate connection event const connectHandler = mockRedisInstance.on.mock.calls.find( call => call[0] === 'connect' )?.[1]; if (connectHandler) { connectHandler(); } expect(mockLogger.info).toHaveBeenCalledWith('Redis connected'); }); it('should handle connection error events', async () => { await initializeRedis(); // Simulate error event const errorHandler = mockRedisInstance.on.mock.calls.find( call => call[0] === 'error' )?.[1]; const testError = new Error('Connection failed'); if (errorHandler) { errorHandler(testError); } expect(mockLogger.error).toHaveBeenCalledWith('Redis error', { error: testError }); }); it('should handle reconnection events', async () => { await initializeRedis(); // Simulate reconnecting event const reconnectingHandler = mockRedisInstance.on.mock.calls.find( call => call[0] === 'reconnecting' )?.[1]; if (reconnectingHandler) { reconnectingHandler(); } expect(mockLogger.info).toHaveBeenCalledWith('Redis reconnecting', { delay: undefined }); }); it('should handle initialization failures gracefully', async () => { const initError = new Error('Redis initialization failed'); (Redis as jest.MockedClass<typeof Redis>).mockImplementation(() => { throw initError; }); await expect(initializeRedis()).rejects.toThrow('Redis initialization failed'); expect(mockLogger.error).toHaveBeenCalledWith('Failed to initialize Redis', { error: initError }); }); }); describe('Redis operations', () => { beforeEach(async () => { await initializeRedis(); }); describe('string operations', () => { it('should perform GET operations', async () => { const testKey = 'test:key'; const testValue = 'test-value'; mockRedisInstance.get.mockResolvedValue(testValue); const result = await redis.get(testKey); expect(result).toBe(testValue); expect(mockRedisInstance.get).toHaveBeenCalledWith(testKey); }); it('should perform SET operations', async () => { const testKey = 'test:key'; const testValue = 'test-value'; mockRedisInstance.set.mockResolvedValue('OK'); const result = await redis.set(testKey, testValue); expect(result).toBe('OK'); expect(mockRedisInstance.set).toHaveBeenCalledWith(testKey, testValue); }); it('should perform SETEX operations with expiration', async () => { const testKey = 'test:key'; const testValue = 'test-value'; const ttlSeconds = 3600; mockRedisInstance.setex.mockResolvedValue('OK'); const result = await redis.setex(testKey, ttlSeconds, testValue); expect(result).toBe('OK'); expect(mockRedisInstance.setex).toHaveBeenCalledWith(testKey, ttlSeconds, testValue); }); it('should handle DEL operations', async () => { const testKeys = ['key1', 'key2', 'key3']; mockRedisInstance.del.mockResolvedValue(testKeys.length); const result = await redis.del(...testKeys); expect(result).toBe(testKeys.length); expect(mockRedisInstance.del).toHaveBeenCalledWith(...testKeys); }); it('should handle EXISTS operations', async () => { const testKey = 'test:key'; mockRedisInstance.exists.mockResolvedValue(1); const result = await redis.exists(testKey); expect(result).toBe(1); expect(mockRedisInstance.exists).toHaveBeenCalledWith(testKey); }); }); describe('expiration operations', () => { it('should set key expiration with EXPIRE', async () => { const testKey = 'test:key'; const ttlSeconds = 3600; mockRedisInstance.expire.mockResolvedValue(1); const result = await redis.expire(testKey, ttlSeconds); expect(result).toBe(1); expect(mockRedisInstance.expire).toHaveBeenCalledWith(testKey, ttlSeconds); }); it('should get TTL for keys', async () => { const testKey = 'test:key'; const ttlSeconds = 1800; mockRedisInstance.ttl.mockResolvedValue(ttlSeconds); const result = await redis.ttl(testKey); expect(result).toBe(ttlSeconds); expect(mockRedisInstance.ttl).toHaveBeenCalledWith(testKey); }); }); describe('numeric operations', () => { it('should increment values with INCR', async () => { const testKey = 'counter:key'; const newValue = 5; mockRedisInstance.incr.mockResolvedValue(newValue); const result = await redis.incr(testKey); expect(result).toBe(newValue); expect(mockRedisInstance.incr).toHaveBeenCalledWith(testKey); }); }); describe('set operations', () => { it('should add members to sets with SADD', async () => { const testKey = 'test:set'; const members = ['member1', 'member2', 'member3']; mockRedisInstance.sadd.mockResolvedValue(members.length); const result = await redis.sadd(testKey, ...members); expect(result).toBe(members.length); expect(mockRedisInstance.sadd).toHaveBeenCalledWith(testKey, ...members); }); it('should remove members from sets with SREM', async () => { const testKey = 'test:set'; const members = ['member1', 'member2']; mockRedisInstance.srem.mockResolvedValue(members.length); const result = await redis.srem(testKey, ...members); expect(result).toBe(members.length); expect(mockRedisInstance.srem).toHaveBeenCalledWith(testKey, ...members); }); it('should get all set members with SMEMBERS', async () => { const testKey = 'test:set'; const members = ['member1', 'member2', 'member3']; mockRedisInstance.smembers.mockResolvedValue(members); const result = await redis.smembers(testKey); expect(result).toEqual(members); expect(mockRedisInstance.smembers).toHaveBeenCalledWith(testKey); }); }); describe('hash operations', () => { it('should get hash field values with HGET', async () => { const testKey = 'test:hash'; const field = 'field1'; const value = 'value1'; mockRedisInstance.hget.mockResolvedValue(value); const result = await redis.hget(testKey, field); expect(result).toBe(value); expect(mockRedisInstance.hget).toHaveBeenCalledWith(testKey, field); }); it('should set hash field values with HSET', async () => { const testKey = 'test:hash'; const field = 'field1'; const value = 'value1'; mockRedisInstance.hset.mockResolvedValue(1); const result = await redis.hset(testKey, field, value); expect(result).toBe(1); expect(mockRedisInstance.hset).toHaveBeenCalledWith(testKey, field, value); }); it('should get all hash fields with HGETALL', async () => { const testKey = 'test:hash'; const hashData = { field1: 'value1', field2: 'value2' }; mockRedisInstance.hgetall.mockResolvedValue(hashData); const result = await redis.hgetall(testKey); expect(result).toEqual(hashData); expect(mockRedisInstance.hgetall).toHaveBeenCalledWith(testKey); }); }); describe('key pattern operations', () => { it('should find keys matching patterns with KEYS', async () => { const pattern = 'session:*'; const matchingKeys = ['session:123', 'session:456', 'session:789']; mockRedisInstance.keys.mockResolvedValue(matchingKeys); const result = await redis.keys(pattern); expect(result).toEqual(matchingKeys); expect(mockRedisInstance.keys).toHaveBeenCalledWith(pattern); }); }); describe('utility operations', () => { it('should ping Redis server', async () => { mockRedisInstance.ping.mockResolvedValue('PONG'); const result = await redis.ping(); expect(result).toBe('PONG'); expect(mockRedisInstance.ping).toHaveBeenCalled(); }); }); }); describe('Error handling', () => { beforeEach(async () => { await initializeRedis(); }); it('should handle Redis operation errors gracefully', async () => { const testError = new Error('Redis operation failed'); mockRedisInstance.get.mockRejectedValue(testError); await expect(redis.get('test:key')).rejects.toThrow('Redis operation failed'); }); it('should handle connection timeouts', async () => { const timeoutError = new Error('Connection timeout'); timeoutError.name = 'TimeoutError'; mockRedisInstance.ping.mockRejectedValue(timeoutError); await expect(redis.ping()).rejects.toThrow('Connection timeout'); }); it('should handle network errors during operations', async () => { const networkError = new Error('Network unreachable'); networkError.name = 'NetworkError'; mockRedisInstance.set.mockRejectedValue(networkError); await expect(redis.set('test:key', 'value')).rejects.toThrow('Network unreachable'); }); }); describe('Connection lifecycle', () => { it('should handle graceful disconnection', async () => { await initializeRedis(); mockRedisInstance.disconnect.mockResolvedValue(undefined); await expect(redis.disconnect()).resolves.not.toThrow(); expect(mockRedisInstance.disconnect).toHaveBeenCalled(); }); it('should handle connection status checks', async () => { await initializeRedis(); expect(redis.status).toBe('ready'); }); }); describe('Performance considerations', () => { beforeEach(async () => { await initializeRedis(); }); it('should handle concurrent operations efficiently', async () => { const concurrentOperations = Array.from({ length: 100 }, (_, i) => { mockRedisInstance.get.mockResolvedValue(`value-${i}`); return redis.get(`key-${i}`); }); const results = await Promise.all(concurrentOperations); expect(results).toHaveLength(100); expect(results.every((result, i) => result === `value-${i}`)).toBe(true); expect(mockRedisInstance.get).toHaveBeenCalledTimes(100); }); it('should handle large batch operations', async () => { const largeKeySet = Array.from({ length: 1000 }, (_, i) => `key-${i}`); mockRedisInstance.del.mockResolvedValue(largeKeySet.length); const result = await redis.del(...largeKeySet); expect(result).toBe(largeKeySet.length); expect(mockRedisInstance.del).toHaveBeenCalledWith(...largeKeySet); }); }); describe('Data type handling', () => { beforeEach(async () => { await initializeRedis(); }); it('should handle JSON serialization for complex objects', async () => { const testKey = 'test:json'; const complexObject = { id: 123, name: 'Test Object', metadata: { created: new Date().toISOString(), tags: ['test', 'object'], }, enabled: true, }; const serialized = JSON.stringify(complexObject); mockRedisInstance.set.mockResolvedValue('OK'); mockRedisInstance.get.mockResolvedValue(serialized); // Store complex object await redis.set(testKey, serialized); expect(mockRedisInstance.set).toHaveBeenCalledWith(testKey, serialized); // Retrieve and deserialize const retrieved = await redis.get(testKey); const deserialized = JSON.parse(retrieved!); expect(deserialized).toEqual(complexObject); }); it('should handle binary data appropriately', async () => { const testKey = 'test:binary'; const binaryData = Buffer.from('binary test data', 'utf8').toString('base64'); mockRedisInstance.set.mockResolvedValue('OK'); mockRedisInstance.get.mockResolvedValue(binaryData); await redis.set(testKey, binaryData); const result = await redis.get(testKey); expect(result).toBe(binaryData); expect(Buffer.from(result!, 'base64').toString('utf8')).toBe('binary test data'); }); }); describe('Memory management', () => { beforeEach(async () => { await initializeRedis(); }); it('should handle memory-efficient operations for large datasets', async () => { // Simulate large dataset operations const largeDataOperations = Array.from({ length: 50 }, (_, i) => { const largeValue = 'x'.repeat(1024 * 10); // 10KB per value mockRedisInstance.setex.mockResolvedValue('OK'); return redis.setex(`large:key:${i}`, 3600, largeValue); }); const results = await Promise.all(largeDataOperations); expect(results.every(result => result === 'OK')).toBe(true); expect(mockRedisInstance.setex).toHaveBeenCalledTimes(50); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/perfecxion-ai/secure-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server