import { describe, it, expect } from 'vitest'; import { MemoryCache } from '../src/proxymodel/cache.js'; describe('MemoryCache', () => { it('returns computed value on cache miss', async () => { const cache = new MemoryCache(); const value = await cache.getOrCompute('key1', async () => 'computed'); expect(value).toBe('computed'); }); it('returns cached value on cache hit', async () => { const cache = new MemoryCache(); let callCount = 0; const compute = async () => { callCount++; return 'computed'; }; await cache.getOrCompute('key1', compute); const value = await cache.getOrCompute('key1', compute); expect(value).toBe('computed'); expect(callCount).toBe(1); // Only computed once }); it('get/set work for manual cache operations', async () => { const cache = new MemoryCache(); expect(await cache.get('missing')).toBeNull(); await cache.set('key1', 'value1'); expect(await cache.get('key1')).toBe('value1'); }); it('hash produces consistent short hashes', () => { const cache = new MemoryCache(); const hash1 = cache.hash('hello world'); const hash2 = cache.hash('hello world'); const hash3 = cache.hash('different content'); expect(hash1).toBe(hash2); expect(hash1).not.toBe(hash3); expect(hash1).toHaveLength(16); }); it('evicts oldest entry when at capacity', async () => { const cache = new MemoryCache({ maxEntries: 3 }); await cache.set('a', '1'); await cache.set('b', '2'); await cache.set('c', '3'); expect(cache.size).toBe(3); // Adding 4th should evict 'a' (oldest) await cache.set('d', '4'); expect(cache.size).toBe(3); expect(await cache.get('a')).toBeNull(); expect(await cache.get('b')).toBe('2'); expect(await cache.get('d')).toBe('4'); }); it('accessing an entry refreshes its LRU position', async () => { const cache = new MemoryCache({ maxEntries: 3 }); await cache.set('a', '1'); await cache.set('b', '2'); await cache.set('c', '3'); // Access 'a' to refresh it await cache.get('a'); // Adding 'd' should evict 'b' (now oldest), not 'a' await cache.set('d', '4'); expect(await cache.get('a')).toBe('1'); expect(await cache.get('b')).toBeNull(); }); it('getOrCompute refreshes LRU position on hit', async () => { const cache = new MemoryCache({ maxEntries: 3 }); await cache.set('a', '1'); await cache.set('b', '2'); await cache.set('c', '3'); // Hit 'a' via getOrCompute await cache.getOrCompute('a', async () => 'should not run'); // Evict: 'b' should go, not 'a' await cache.set('d', '4'); expect(await cache.get('a')).toBe('1'); expect(await cache.get('b')).toBeNull(); }); it('clear removes all entries', async () => { const cache = new MemoryCache(); await cache.set('a', '1'); await cache.set('b', '2'); expect(cache.size).toBe(2); cache.clear(); expect(cache.size).toBe(0); expect(await cache.get('a')).toBeNull(); }); });