717ae1031e
Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/ binary modes, condensed snapshots, value relations) with 39 rigor test campaigns. Includes fixes for snapshot binary writer/reader format mismatch (snapshot-of-snapshot corruption), possibility write-boundary validation, empty-graph snapshot serialization, relation lookup cache direction collision, config-redefinition cache invalidation, binary threshold semantics, defeasible compiled routing, and comparator reason whitelisting.
129 lines
5.1 KiB
JavaScript
129 lines
5.1 KiB
JavaScript
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');
|