Skip to main content
Glama
cache.test.ts9.89 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { Cache, createCache, CachePresets } from './cache.js'; describe('Cache', () => { let cache: Cache; beforeEach(() => { cache = new Cache(); }); afterEach(() => { cache.destroy(); }); describe('Basic Operations', () => { it('should store and retrieve values', () => { cache.set('key1', 'value1'); expect(cache.get('key1')).toBe('value1'); }); it('should return undefined for non-existent keys', () => { expect(cache.get('nonexistent')).toBeUndefined(); }); 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 entries', () => { cache.set('key1', 'value1'); expect(cache.has('key1')).toBe(true); const deleted = cache.delete('key1'); expect(deleted).toBe(true); expect(cache.has('key1')).toBe(false); expect(cache.get('key1')).toBeUndefined(); }); it('should return false when deleting non-existent key', () => { const deleted = cache.delete('nonexistent'); expect(deleted).toBe(false); }); it('should clear all entries', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); expect(cache.size()).toBe(2); cache.clear(); expect(cache.size()).toBe(0); expect(cache.has('key1')).toBe(false); expect(cache.has('key2')).toBe(false); }); it('should track cache size', () => { expect(cache.size()).toBe(0); cache.set('key1', 'value1'); expect(cache.size()).toBe(1); cache.set('key2', 'value2'); expect(cache.size()).toBe(2); cache.delete('key1'); expect(cache.size()).toBe(1); }); it('should get all keys', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); const keys = cache.keys(); expect(keys).toContain('key1'); expect(keys).toContain('key2'); expect(keys).toHaveLength(2); }); it('should get all values', () => { cache.set('key1', 'value1'); cache.set('key2', 'value2'); const values = cache.values(); expect(values).toContain('value1'); expect(values).toContain('value2'); expect(values).toHaveLength(2); }); }); describe('TTL (Time-To-Live)', () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); it('should expire entries after TTL', () => { cache = new Cache({ defaultTTL: 1000, enableCleanup: false }); cache.set('key1', 'value1'); expect(cache.get('key1')).toBe('value1'); // Advance time by 1001ms (past TTL) vi.advanceTimersByTime(1001); expect(cache.get('key1')).toBeUndefined(); expect(cache.has('key1')).toBe(false); }); it('should use custom TTL for individual entries', () => { cache = new Cache({ defaultTTL: 5000, enableCleanup: false }); cache.set('key1', 'value1', 1000); // Custom TTL of 1 second cache.set('key2', 'value2'); // Uses default TTL of 5 seconds // Advance time by 1001ms vi.advanceTimersByTime(1001); expect(cache.get('key1')).toBeUndefined(); // Should be expired expect(cache.get('key2')).toBe('value2'); // Should still exist }); it('should handle entries with no TTL (never expire)', () => { cache = new Cache({ defaultTTL: 0, enableCleanup: false }); cache.set('key1', 'value1'); // Advance time significantly vi.advanceTimersByTime(10000); expect(cache.get('key1')).toBe('value1'); }); it('should manually cleanup expired entries', () => { cache = new Cache({ defaultTTL: 1000, enableCleanup: false }); cache.set('key1', 'value1'); cache.set('key2', 'value2'); expect(cache.size()).toBe(2); // Advance time past TTL vi.advanceTimersByTime(1001); const cleanedCount = cache.cleanup(); expect(cleanedCount).toBe(2); expect(cache.size()).toBe(0); }); }); describe('Size Limits and Eviction', () => { it('should evict entries when max size is reached (LRU)', () => { cache = new Cache({ maxSize: 2, evictionPolicy: 'lru', enableCleanup: false }); cache.set('key1', 'value1'); cache.set('key2', 'value2'); expect(cache.size()).toBe(2); // Access key1 to make it more recently used cache.get('key1'); // Add third entry, should evict key2 (least recently used) cache.set('key3', 'value3'); expect(cache.size()).toBe(2); expect(cache.has('key1')).toBe(true); expect(cache.has('key2')).toBe(false); expect(cache.has('key3')).toBe(true); }); it('should evict entries when max size is reached (FIFO)', () => { cache = new Cache({ maxSize: 2, evictionPolicy: 'fifo', enableCleanup: false }); cache.set('key1', 'value1'); cache.set('key2', 'value2'); expect(cache.size()).toBe(2); // Access key1 (shouldn't matter for FIFO) cache.get('key1'); // Add third entry, should evict key1 (first in) cache.set('key3', 'value3'); expect(cache.size()).toBe(2); expect(cache.has('key1')).toBe(false); expect(cache.has('key2')).toBe(true); expect(cache.has('key3')).toBe(true); }); it('should handle updating existing keys without eviction', () => { cache = new Cache({ maxSize: 2, enableCleanup: false }); cache.set('key1', 'value1'); cache.set('key2', 'value2'); expect(cache.size()).toBe(2); // Update existing key cache.set('key1', 'updated_value1'); expect(cache.size()).toBe(2); expect(cache.get('key1')).toBe('updated_value1'); expect(cache.has('key2')).toBe(true); }); }); describe('Statistics', () => { it('should track hits and misses', () => { cache.set('key1', 'value1'); // Hit cache.get('key1'); // Miss cache.get('nonexistent'); const stats = cache.getStats(); expect(stats.hits).toBe(1); expect(stats.misses).toBe(1); expect(stats.hitRate).toBe(50); }); it('should track evictions', () => { cache = new Cache({ maxSize: 1, enableCleanup: false }); cache.set('key1', 'value1'); cache.set('key2', 'value2'); // Should evict key1 const stats = cache.getStats(); expect(stats.evictions).toBe(1); }); it('should track expirations', () => { vi.useFakeTimers(); cache = new Cache({ defaultTTL: 1000, enableCleanup: false }); cache.set('key1', 'value1'); // Advance time past TTL vi.advanceTimersByTime(1001); // Try to access expired entry cache.get('key1'); const stats = cache.getStats(); expect(stats.expirations).toBe(1); vi.useRealTimers(); }); it('should reset statistics', () => { cache.set('key1', 'value1'); cache.get('key1'); // Hit cache.get('nonexistent'); // Miss let stats = cache.getStats(); expect(stats.hits).toBe(1); expect(stats.misses).toBe(1); cache.resetStats(); stats = cache.getStats(); expect(stats.hits).toBe(0); expect(stats.misses).toBe(0); expect(stats.hitRate).toBe(0); }); }); describe('Automatic Cleanup', () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); it('should automatically cleanup expired entries', () => { cache = new Cache({ defaultTTL: 1000, enableCleanup: true, cleanupInterval: 500 }); cache.set('key1', 'value1'); expect(cache.size()).toBe(1); // Advance time past TTL but before cleanup interval vi.advanceTimersByTime(1001); expect(cache.size()).toBe(1); // Still there, cleanup hasn't run // Advance time to trigger cleanup vi.advanceTimersByTime(500); expect(cache.size()).toBe(0); // Should be cleaned up }); it('should stop cleanup timer when destroyed', () => { cache = new Cache({ enableCleanup: true, cleanupInterval: 100 }); const clearIntervalSpy = vi.spyOn(global, 'clearInterval'); cache.destroy(); expect(clearIntervalSpy).toHaveBeenCalled(); }); }); describe('Type Safety', () => { it('should work with typed values', () => { interface User { id: number; name: string; } const userCache = new Cache<User>(); const user: User = { id: 1, name: 'John' }; userCache.set('user1', user); const retrieved = userCache.get('user1'); expect(retrieved).toEqual(user); expect(retrieved?.id).toBe(1); expect(retrieved?.name).toBe('John'); }); }); }); describe('createCache', () => { it('should create a cache instance', () => { const cache = createCache({ maxSize: 100 }); expect(cache).toBeInstanceOf(Cache); cache.set('test', 'value'); expect(cache.get('test')).toBe('value'); cache.destroy(); }); }); describe('CachePresets', () => { it('should provide predefined configurations', () => { expect(CachePresets.small.maxSize).toBe(100); expect(CachePresets.medium.maxSize).toBe(500); expect(CachePresets.large.maxSize).toBe(2000); expect(CachePresets.persistent.maxSize).toBe(1000); expect(CachePresets.small.evictionPolicy).toBe('lru'); expect(CachePresets.persistent.evictionPolicy).toBe('fifo'); }); it('should work with preset configurations', () => { const cache = new Cache(CachePresets.small); cache.set('test', 'value'); expect(cache.get('test')).toBe('value'); const stats = cache.getStats(); expect(stats.maxSize).toBe(100); cache.destroy(); }); });

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/vltansky/cursor-conversations-mcp'

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