stock-bot/libs/core/cache/test/redis-cache.test.ts
2025-06-26 16:12:27 -04:00

210 lines
6 KiB
TypeScript

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();
});
});
});