import { test, describe, it, beforeEach } from 'node:test'; import assert from 'node:assert'; import { Arbiter } from '../../src/core/Arbiter.js'; import { ChainRule } from '../../src/authorization/rules/ChainRule.js'; describe('Cache Memory Improvements (SimpleLRUCache default, injectable factory)', () => { let arbiter; let chainRule; beforeEach(() => { arbiter = new Arbiter({ directCheckCacheSize: 1000, directCheckCacheTTL: 60000 }); chainRule = new ChainRule(arbiter); }); it('should use SimpleLRUCache (default cacheFactory) for chain caches', () => { // Verify ChainRule uses the default SimpleLRUCache assert.ok(chainRule.chainResultCache.constructor.name === 'SimpleLRUCache'); // Verify cache capacity assert.strictEqual(chainRule.maxCacheSize, 2000); }); it('should use SimpleLRUCache for direct check cache', () => { // Verify Arbiter uses the default SimpleLRUCache assert.ok(arbiter.directCheckCache.constructor.name === 'SimpleLRUCache'); // Verify cache capacity assert.strictEqual(arbiter.directCheckCacheSize, 1000); }); it('should use SimpleLRUCache for relation manager caches', () => { // Cache state lives in RelationCaches (RF-08): reach it via `_caches`. assert.ok(arbiter.relationManager._caches.relationLookupCache.constructor.name === 'SimpleLRUCache'); assert.ok(arbiter.relationManager._caches.valueLookupCache.constructor.name === 'SimpleLRUCache'); // Verify cache capacity assert.strictEqual(arbiter.relationManager._caches.maxCacheSize, 10000); }); it('should use SimpleLRUCache for value manager caches', () => { // Verify ValueManager uses the default SimpleLRUCache assert.ok(arbiter.valueManager.blurredValueCache.constructor.name === 'SimpleLRUCache'); assert.ok(arbiter.valueManager.distributionCache.constructor.name === 'SimpleLRUCache'); }); it('should use SimpleLRUCache for similarity manager cache', () => { // SimilarityManager is only constructed when similarityParams are provided const simArbiter = new Arbiter({ similarityParams: {} }); assert.ok(simArbiter.similarityManager.nodeKeyToVector.constructor.name === 'SimpleLRUCache'); }); it('should handle cache eviction automatically', () => { // Test that the default cache handles eviction automatically const cache = chainRule.chainResultCache; const initialSize = cache.size(); // Fill cache beyond capacity for (let i = 0; i < chainRule.maxCacheSize + 100; i++) { cache.set(`key_${i}`, { result: { possibility: 0.5 }, timestamp: Date.now() }); } // Cache should not exceed capacity assert.ok(cache.size() <= chainRule.maxCacheSize); console.log(`Cache size after overflow: ${cache.size()} (max: ${chainRule.maxCacheSize})`); }); it('should track eviction statistics', () => { // Test that eviction statistics are tracked const cache = chainRule.chainResultCache; // Fill cache beyond capacity to trigger evictions for (let i = 0; i < chainRule.maxCacheSize + 50; i++) { cache.set(`key_${i}`, { result: { possibility: 0.5 }, timestamp: Date.now() }); } // Check if eviction stats are tracked if (chainRule.stats) { console.log(`Chain result cache evictions: ${chainRule.stats.evictedResults || 0}`); console.log(`Chain path cache evictions: ${chainRule.stats.evictedPaths || 0}`); } }); it('should provide better memory management than Map', () => { // Compare memory usage patterns const mapCache = new Map(); const hyperbolicCache = chainRule.chainResultCache; // Fill both caches for (let i = 0; i < 1000; i++) { const value = { result: { possibility: 0.5 }, timestamp: Date.now() }; mapCache.set(`key_${i}`, value); hyperbolicCache.set(`key_${i}`, value); } // Map grows indefinitely, the default cache is bounded assert.ok(mapCache.size === 1000); assert.ok(hyperbolicCache.size() <= chainRule.maxCacheSize); console.log(`Map size: ${mapCache.size}`); console.log(`SimpleLRUCache size: ${hyperbolicCache.size()}`); }); it('should handle TTL without manual cleanup', () => { // Test that TTL is handled without manual cleanup const cache = arbiter.directCheckCache; // Add entries with different timestamps const now = Date.now(); cache.set('key1', { result: { possibility: 0.5 }, timestamp: now }); cache.set('key2', { result: { possibility: 0.5 }, timestamp: now - 70000 }); // Expired // SimpleLRUCache keeps both entries; TTL is enforced at read time // No manual cleanup needed assert.ok(cache.has('key1')); assert.ok(cache.has('key2')); // Still in cache, but will be evicted based on frequency }); }); console.log('✅ Cache Memory Improvements Test Suite'); console.log('📊 Benefits:'); console.log(' - Automatic eviction based on frequency and recency'); console.log(' - No manual TTL cleanup needed'); console.log(' - Bounded memory usage'); console.log(' - Better cache hit rates through intelligent eviction'); console.log(' - Protection against memory leaks');