Skip to main content
Glama
CacheManager.test.ts5.89 kB
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'); }); }); });

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/alirezarezvani/confluence-mcp-server'

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