import { beforeEach, describe, expect, it } from 'bun:test'; import { RedisCache } from '../src/redis-cache'; import type { CacheOptions } from '../src/types'; describe('RedisCache', () => { let cache: RedisCache; beforeEach(() => { const options: CacheOptions = { keyPrefix: 'test:', ttl: 3600, redisConfig: { host: 'localhost', port: 6379 }, }; cache = new RedisCache(options); }); describe('Core functionality', () => { it('should create cache instance', () => { expect(cache).toBeDefined(); expect(cache.isReady).toBeDefined(); expect(cache.get).toBeDefined(); expect(cache.set).toBeDefined(); }); it('should have stats tracking', () => { const stats = cache.getStats(); expect(stats).toMatchObject({ hits: 0, misses: 0, errors: 0, hitRate: 0, total: 0, }); expect(stats.uptime).toBeGreaterThanOrEqual(0); }); }); describe('Basic operations', () => { it('should handle get/set operations', async () => { const key = 'test-key'; const value = { foo: 'bar' }; // Should return null for non-existent key const miss = await cache.get(key); expect(miss).toBeNull(); // Should set and retrieve value await cache.set(key, value); const retrieved = await cache.get(key); expect(retrieved).toEqual(value); // Should delete key await cache.del(key); const deleted = await cache.get(key); expect(deleted).toBeNull(); }); it('should check key existence', async () => { const key = 'existence-test'; expect(await cache.exists(key)).toBe(false); await cache.set(key, 'value'); expect(await cache.exists(key)).toBe(true); await cache.del(key); expect(await cache.exists(key)).toBe(false); }); it('should handle TTL in set operations', async () => { const key = 'ttl-test'; const value = 'test-value'; // Set with custom TTL as number await cache.set(key, value, 1); expect(await cache.get(key)).toBe(value); // Set with custom TTL in options await cache.set(key, value, { ttl: 2 }); expect(await cache.get(key)).toBe(value); }); }); describe('Advanced set options', () => { it('should handle onlyIfExists option', async () => { const key = 'conditional-test'; const value1 = 'value1'; const value2 = 'value2'; // Should not set if key doesn't exist await cache.set(key, value1, { onlyIfExists: true }); expect(await cache.get(key)).toBeNull(); // Create the key await cache.set(key, value1); // Should update if key exists await cache.set(key, value2, { onlyIfExists: true }); expect(await cache.get(key)).toBe(value2); }); it('should handle onlyIfNotExists option', async () => { const key = 'nx-test'; const value1 = 'value1'; const value2 = 'value2'; // Should set if key doesn't exist await cache.set(key, value1, { onlyIfNotExists: true }); expect(await cache.get(key)).toBe(value1); // Should not update if key exists await cache.set(key, value2, { onlyIfNotExists: true }); expect(await cache.get(key)).toBe(value1); }); it('should handle preserveTTL option', async () => { const key = 'preserve-ttl-test'; const value1 = 'value1'; const value2 = 'value2'; // Set with short TTL await cache.set(key, value1, 10); // Update preserving TTL await cache.set(key, value2, { preserveTTL: true }); expect(await cache.get(key)).toBe(value2); }); it('should handle getOldValue option', async () => { const key = 'old-value-test'; const value1 = 'value1'; const value2 = 'value2'; // Should return null when no old value const oldValue1 = await cache.set(key, value1, { getOldValue: true }); expect(oldValue1).toBeNull(); // Should return old value const oldValue2 = await cache.set(key, value2, { getOldValue: true }); expect(oldValue2).toBe(value1); }); }); describe('Error handling', () => { it('should handle errors gracefully in get', async () => { // Force an error by using invalid JSON const badCache = new RedisCache({ keyPrefix: 'bad:', redisConfig: { host: 'localhost', port: 6379 }, }); // This would normally throw but should return null const result = await badCache.get('non-existent'); expect(result).toBeNull(); // Check stats updated const stats = badCache.getStats(); expect(stats.misses).toBe(1); }); }); describe('Pattern operations', () => { it('should find keys by pattern', async () => { // Clear first to ensure clean state await cache.clear(); await cache.set('user:1', { id: 1 }); await cache.set('user:2', { id: 2 }); await cache.set('post:1', { id: 1 }); const userKeys = await cache.keys('user:*'); expect(userKeys).toHaveLength(2); expect(userKeys).toContain('user:1'); expect(userKeys).toContain('user:2'); const allKeys = await cache.keys('*'); expect(allKeys.length).toBeGreaterThanOrEqual(3); expect(allKeys).toContain('user:1'); expect(allKeys).toContain('user:2'); expect(allKeys).toContain('post:1'); }); it('should clear all keys with prefix', async () => { await cache.set('key1', 'value1'); await cache.set('key2', 'value2'); await cache.clear(); const keys = await cache.keys('*'); expect(keys).toHaveLength(0); }); }); describe('Health checks', () => { it('should check health', async () => { const healthy = await cache.health(); expect(healthy).toBe(true); }); it('should check if ready', () => { // May not be ready immediately const ready = cache.isReady(); expect(typeof ready).toBe('boolean'); }); it('should wait for ready', async () => { await expect(cache.waitForReady(1000)).resolves.toBeUndefined(); }); }); });