Files
core/tests/rules/relation-cache-extraction.test.js
T
John Dvorak 717ae1031e initial commit: @arbiter/core authorization engine with js-rigor hardening
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.
2026-07-31 13:44:06 -07:00

108 lines
4.4 KiB
JavaScript

/**
* relation-cache-extraction.test.js — verifies RF-08 deletion test closure.
*
* Before this refactor, RelationManager aliased 7 cache fields from
* RelationCaches (this._relationLookupCache, this._valueLookupCache, etc.).
* Removing RelationCaches.js from disk without removing the aliases
* would have left broken references. This test confirms:
* - The aliases no longer exist on RelationManager
* - All cache state lives under RelationManager._caches
* - Functional behavior is unchanged: relations still work, caches still
* populate and serve lookups
*/
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { Arbiter } from '../../src/index.js';
describe('RF-08: RelationManager cache extraction', () => {
it('does not alias cache fields (deletion test)', () => {
const arbiter = new Arbiter();
const rm = arbiter.relationManager;
// The seven aliased fields that used to live on RelationManager
// are gone. Each must be undefined.
assert.equal(rm._maxCacheSize, undefined);
assert.equal(rm._relationLookupCache, undefined);
assert.equal(rm._valueLookupCache, undefined);
assert.equal(rm._relationLookupCacheKeys, undefined);
assert.equal(rm._valueLookupCacheKeys, undefined);
assert.equal(rm._valueRelationsBySrcCache, undefined);
assert.equal(rm._valueRelationsByDstCache, undefined);
assert.equal(rm._valueRelationsByNameCache, undefined);
// The only cache surface is now _caches (the RelationCaches instance).
assert.ok(rm._caches);
assert.ok(rm._caches.relationLookupCache);
assert.ok(rm._caches.valueLookupCache);
assert.ok(rm._caches.valueRelationsBySrcCache);
assert.ok(rm._caches.valueRelationsByDstCache);
assert.ok(rm._caches.valueRelationsByNameCache);
});
it('functional cache behavior is preserved', () => {
const arbiter = new Arbiter();
arbiter.addNode('user', 'user');
arbiter.addNode('doc', 'document');
arbiter.addRelation('user', 'can_read', 'doc', 1.0);
const srcId = arbiter.nodeIdByKey.get('user');
const dstId = arbiter.nodeIdByKey.get('doc');
// First call populates cache
const r1 = arbiter.relationManager.getDirectRelation(srcId, 'can_read', dstId);
assert.ok(r1);
const cacheKey = arbiter.relationManager._makeDirectCacheKey(srcId, 'can_read', dstId);
assert.ok(arbiter.relationManager._caches.relationLookupCache.has(cacheKey));
// Second call hits cache
const r2 = arbiter.relationManager.getDirectRelation(srcId, 'can_read', dstId);
assert.equal(r1, r2);
// Removal invalidates
arbiter.removeRelation('user', 'can_read', 'doc');
assert.equal(arbiter.relationManager._caches.relationLookupCache.has(cacheKey), false);
});
it('value relation caches still work end-to-end', () => {
const arbiter = new Arbiter();
arbiter.addNode('user', 'user');
arbiter.addNode('account', 'account');
arbiter.addRelation('user', 'has_balance', 'account', 1.0, { value: 100 });
const srcId = arbiter.nodeIdByKey.get('user');
const dstId = arbiter.nodeIdByKey.get('account');
// Populate
const v1 = arbiter.relationManager.getValueRelation(srcId, 'has_balance', dstId);
assert.ok(v1);
assert.equal(v1.pointValue, 100);
const cacheKey = arbiter.relationManager._makeValueCacheKey(srcId, 'has_balance', dstId);
assert.ok(arbiter.relationManager._caches.valueLookupCache.has(cacheKey));
// Update — should invalidate
arbiter.relationManager._modifyRelation('user', 'has_balance', 'account', { value: 200 });
assert.equal(arbiter.relationManager._caches.valueLookupCache.has(cacheKey), false);
// Re-fetch reflects new value
const v2 = arbiter.relationManager.getValueRelation(srcId, 'has_balance', dstId);
assert.equal(v2.pointValue, 200);
});
it('cache stats reference _caches.size() correctly', () => {
const arbiter = new Arbiter();
arbiter.addNode('a', 'x');
arbiter.addNode('b', 'x');
arbiter.addRelation('a', 'rel', 'b', 1.0);
const srcId = arbiter.nodeIdByKey.get('a');
const dstId = arbiter.nodeIdByKey.get('b');
arbiter.relationManager.getDirectRelation(srcId, 'rel', dstId);
const stats = arbiter.relationManager.getCacheStats();
assert.ok(typeof stats.relationCacheSize === 'number');
assert.ok(typeof stats.valueCacheSize === 'number');
assert.ok(stats.relationCacheSize >= 1);
});
});