git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
257 lines
6.5 KiB
JavaScript
257 lines
6.5 KiB
JavaScript
/**
|
|
* Unit tests for ContextCache
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { ContextCache } from '../../../src/cache/context-cache.js';
|
|
|
|
describe('ContextCache', () => {
|
|
let cache;
|
|
|
|
beforeEach(() => {
|
|
cache = new ContextCache({
|
|
maxSize: 5,
|
|
ttl: 1000 // 1 second for testing
|
|
});
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should create cache with default options', () => {
|
|
const defaultCache = new ContextCache();
|
|
expect(defaultCache.maxSize).toBe(100);
|
|
expect(defaultCache.ttl).toBe(3600000);
|
|
});
|
|
|
|
it('should accept custom options', () => {
|
|
expect(cache.maxSize).toBe(5);
|
|
expect(cache.ttl).toBe(1000);
|
|
});
|
|
|
|
it('should initialize empty cache', () => {
|
|
expect(cache.cache.size).toBe(0);
|
|
});
|
|
|
|
it('should initialize stats', () => {
|
|
const stats = cache.getStats();
|
|
expect(stats.hits).toBe(0);
|
|
expect(stats.misses).toBe(0);
|
|
expect(stats.evictions).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('set and get', () => {
|
|
it('should store and retrieve value', () => {
|
|
cache.set('key1', 'value1');
|
|
const result = cache.get('key1');
|
|
expect(result).toBe('value1');
|
|
});
|
|
|
|
it('should return null for non-existent key', () => {
|
|
const result = cache.get('nonexistent');
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should update existing key', () => {
|
|
cache.set('key1', 'value1');
|
|
cache.set('key1', 'value2');
|
|
expect(cache.get('key1')).toBe('value2');
|
|
expect(cache.cache.size).toBe(1);
|
|
});
|
|
|
|
it('should store complex objects', () => {
|
|
const obj = { nested: { data: [1, 2, 3] } };
|
|
cache.set('complex', obj);
|
|
expect(cache.get('complex')).toEqual(obj);
|
|
});
|
|
});
|
|
|
|
describe('TTL (Time To Live)', () => {
|
|
it('should return null for expired entries', async () => {
|
|
cache.set('key1', 'value1');
|
|
|
|
// Wait for TTL to expire
|
|
await new Promise(resolve => setTimeout(resolve, 1100));
|
|
|
|
const result = cache.get('key1');
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should not return expired entries in has()', async () => {
|
|
cache.set('key1', 'value1');
|
|
expect(cache.has('key1')).toBe(true);
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1100));
|
|
|
|
expect(cache.has('key1')).toBe(false);
|
|
});
|
|
|
|
it('should delete expired entries', async () => {
|
|
cache.set('key1', 'value1');
|
|
expect(cache.cache.size).toBe(1);
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1100));
|
|
cache.get('key1'); // Triggers cleanup
|
|
|
|
expect(cache.cache.size).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('eviction', () => {
|
|
it('should evict LRU entry when at capacity', () => {
|
|
// Fill cache to capacity
|
|
for (let i = 0; i < 5; i++) {
|
|
cache.set(`key${i}`, `value${i}`);
|
|
}
|
|
|
|
expect(cache.cache.size).toBe(5);
|
|
|
|
// Access key1 to make key0 the LRU
|
|
cache.get('key1');
|
|
|
|
// Add new entry, should evict key0
|
|
cache.set('key5', 'value5');
|
|
|
|
expect(cache.cache.size).toBe(5);
|
|
expect(cache.get('key0')).toBeNull();
|
|
expect(cache.get('key5')).toBe('value5');
|
|
});
|
|
|
|
it('should track eviction stats', () => {
|
|
for (let i = 0; i < 6; i++) {
|
|
cache.set(`key${i}`, `value${i}`);
|
|
}
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.evictions).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('has', () => {
|
|
it('should return true for existing key', () => {
|
|
cache.set('key1', 'value1');
|
|
expect(cache.has('key1')).toBe(true);
|
|
});
|
|
|
|
it('should return false for non-existent key', () => {
|
|
expect(cache.has('nonexistent')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('clear', () => {
|
|
it('should remove all entries', () => {
|
|
cache.set('key1', 'value1');
|
|
cache.set('key2', 'value2');
|
|
|
|
cache.clear();
|
|
|
|
expect(cache.cache.size).toBe(0);
|
|
expect(cache.get('key1')).toBeNull();
|
|
});
|
|
|
|
it('should reset statistics', () => {
|
|
cache.set('key1', 'value1');
|
|
cache.get('key1');
|
|
cache.get('nonexistent');
|
|
|
|
cache.clear();
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.hits).toBe(0);
|
|
expect(stats.misses).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getStats', () => {
|
|
it('should track cache hits', () => {
|
|
cache.set('key1', 'value1');
|
|
cache.get('key1');
|
|
cache.get('key1');
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.hits).toBe(2);
|
|
});
|
|
|
|
it('should track cache misses', () => {
|
|
cache.get('nonexistent1');
|
|
cache.get('nonexistent2');
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.misses).toBe(2);
|
|
});
|
|
|
|
it('should calculate hit rate', () => {
|
|
cache.set('key1', 'value1');
|
|
cache.get('key1'); // hit
|
|
cache.get('key1'); // hit
|
|
cache.get('nonexistent'); // miss
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.hitRate).toBeCloseTo(0.666, 2);
|
|
});
|
|
|
|
it('should include cache size', () => {
|
|
cache.set('key1', 'value1');
|
|
cache.set('key2', 'value2');
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.size).toBe(2);
|
|
});
|
|
|
|
it('should handle zero hit rate', () => {
|
|
cache.get('nonexistent');
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.hitRate).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('access tracking', () => {
|
|
it('should update access count', () => {
|
|
cache.set('key1', 'value1');
|
|
cache.get('key1');
|
|
cache.get('key1');
|
|
|
|
const entry = cache.cache.get('key1');
|
|
expect(entry.accessCount).toBe(2);
|
|
});
|
|
|
|
it('should update last access time', () => {
|
|
cache.set('key1', 'value1');
|
|
const initialAccess = cache.cache.get('key1').lastAccess;
|
|
|
|
// Small delay
|
|
setTimeout(() => {
|
|
cache.get('key1');
|
|
const laterAccess = cache.cache.get('key1').lastAccess;
|
|
expect(laterAccess).toBeGreaterThan(initialAccess);
|
|
}, 10);
|
|
});
|
|
});
|
|
|
|
describe('performance', () => {
|
|
it('should handle 1000 operations quickly', () => {
|
|
const start = Date.now();
|
|
|
|
for (let i = 0; i < 1000; i++) {
|
|
cache.set(`key${i}`, `value${i}`);
|
|
cache.get(`key${i}`);
|
|
}
|
|
|
|
const duration = Date.now() - start;
|
|
expect(duration).toBeLessThan(100); // Less than 100ms
|
|
});
|
|
|
|
it('should maintain performance with large values', () => {
|
|
const largeValue = { data: new Array(1000).fill('x'.repeat(100)) };
|
|
|
|
const start = Date.now();
|
|
for (let i = 0; i < 100; i++) {
|
|
cache.set(`key${i}`, largeValue);
|
|
}
|
|
const duration = Date.now() - start;
|
|
|
|
expect(duration).toBeLessThan(100);
|
|
});
|
|
});
|
|
});
|