Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
256
npm/packages/agentic-synth/tests/unit/cache/context-cache.test.js
vendored
Normal file
256
npm/packages/agentic-synth/tests/unit/cache/context-cache.test.js
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user