import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { CacheManager } from '../../src/cache/CacheManager.js';
describe('CacheManager', () => {
let cache: CacheManager;
beforeEach(() => {
cache = new CacheManager({
maxSize: 10,
defaultTtl: 1000, // 1 second for testing
cleanupInterval: 100, // 100ms for testing
enableStats: true,
});
});
afterEach(() => {
cache.shutdown();
});
describe('Basic Operations', () => {
it('should set and get values', () => {
cache.set('key1', 'value1');
expect(cache.get('key1')).toBe('value1');
});
it('should return null for non-existent keys', () => {
expect(cache.get('nonexistent')).toBeNull();
});
it('should check if key exists', () => {
cache.set('key1', 'value1');
expect(cache.has('key1')).toBe(true);
expect(cache.has('nonexistent')).toBe(false);
});
it('should delete keys', () => {
cache.set('key1', 'value1');
expect(cache.delete('key1')).toBe(true);
expect(cache.get('key1')).toBeNull();
expect(cache.delete('nonexistent')).toBe(false);
});
it('should clear all entries', () => {
cache.set('key1', 'value1');
cache.set('key2', 'value2');
cache.clear();
expect(cache.get('key1')).toBeNull();
expect(cache.get('key2')).toBeNull();
});
});
describe('TTL and Expiration', () => {
it('should expire entries after TTL', async () => {
cache.set('key1', 'value1', 50); // 50ms TTL
expect(cache.get('key1')).toBe('value1');
await new Promise(resolve => setTimeout(resolve, 100));
expect(cache.get('key1')).toBeNull();
});
it('should use default TTL when not specified', async () => {
cache.set('key1', 'value1');
expect(cache.get('key1')).toBe('value1');
await new Promise(resolve => setTimeout(resolve, 1200));
expect(cache.get('key1')).toBeNull();
});
it('should cleanup expired entries automatically', async () => {
cache.set('key1', 'value1', 50);
cache.set('key2', 'value2', 200);
await new Promise(resolve => setTimeout(resolve, 150));
// key1 should be cleaned up, key2 should still exist
expect(cache.get('key1')).toBeNull();
expect(cache.get('key2')).toBe('value2');
});
});
describe('Cache Size Management', () => {
it('should enforce max size limit', () => {
// Fill cache to max size
for (let i = 0; i < 10; i++) {
cache.set(`key${i}`, `value${i}`);
}
// Add one more - should evict LRU
cache.set('key10', 'value10');
// First key should be evicted
expect(cache.get('key0')).toBeNull();
expect(cache.get('key10')).toBe('value10');
});
it('should evict least recently used entries', () => {
// Fill cache
for (let i = 0; i < 10; i++) {
cache.set(`key${i}`, `value${i}`);
}
// Access key1 to make it recently used
cache.get('key1');
// Add new entry
cache.set('new', 'value');
// key0 should be evicted (LRU), key1 should remain
expect(cache.get('key0')).toBeNull();
expect(cache.get('key1')).toBe('value1');
expect(cache.get('new')).toBe('value');
});
});
describe('Statistics', () => {
it('should track cache hits and misses', () => {
cache.set('key1', 'value1');
cache.get('key1'); // hit
cache.get('key1'); // hit
cache.get('nonexistent'); // miss
const stats = cache.getStats();
expect(stats.hits).toBe(2);
expect(stats.misses).toBe(1);
expect(stats.hitRate).toBe(66.67); // 2/3 * 100
});
it('should track sets and deletes', () => {
cache.set('key1', 'value1');
cache.set('key2', 'value2');
cache.delete('key1');
const stats = cache.getStats();
expect(stats.sets).toBe(2);
expect(stats.deletes).toBe(1);
});
});
describe('Advanced Features', () => {
it('should support getOrSet pattern', async () => {
let fetcherCalled = false;
const fetcher = async () => {
fetcherCalled = true;
return 'fetched value';
};
// First call should fetch
const result1 = await cache.getOrSet('key1', fetcher);
expect(result1).toBe('fetched value');
expect(fetcherCalled).toBe(true);
// Second call should use cache
fetcherCalled = false;
const result2 = await cache.getOrSet('key1', fetcher);
expect(result2).toBe('fetched value');
expect(fetcherCalled).toBe(false);
});
it('should invalidate by pattern', () => {
cache.set('user:1', 'user1');
cache.set('user:2', 'user2');
cache.set('post:1', 'post1');
const deleted = cache.invalidatePattern(/^user:/);
expect(deleted).toBe(2);
expect(cache.get('user:1')).toBeNull();
expect(cache.get('user:2')).toBeNull();
expect(cache.get('post:1')).toBe('post1');
});
it('should support cache warming', () => {
const entries = [
{ key: 'key1', value: 'value1' },
{ key: 'key2', value: 'value2', ttl: 500 },
];
cache.warm(entries);
expect(cache.get('key1')).toBe('value1');
expect(cache.get('key2')).toBe('value2');
});
});
describe('Cache Info', () => {
it('should provide detailed cache information', () => {
cache.set('key1', 'value1');
cache.set('key2', { complex: 'object' });
const info = cache.getInfo();
expect(info.size).toBe(2);
expect(info.maxSize).toBe(10);
expect(info.entries).toHaveLength(2);
expect(info.entries[0]).toHaveProperty('key');
expect(info.entries[0]).toHaveProperty('size');
expect(info.entries[0]).toHaveProperty('age');
expect(info.entries[0]).toHaveProperty('accessCount');
});
});
});