Files
core/tests/rules/rule-reachability-integration.test.js
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

178 lines
7.3 KiB
JavaScript

/**
* Test reachability integration with all rule types
*/
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { BigGraphGenerator } from '../helpers/big-graph-generator.js';
import { ChainRule } from '../../src/authorization/rules/ChainRule.js';
import { DirectRule } from '../../src/authorization/rules/DirectRule.js';
import { MultiHopRule } from '../../src/authorization/rules/MultiHopRule.js';
test('verifies all rules can use quick reachability failure checks', async () => {
if (process.env.TEST_DEBUG === '1') console.log('🔍 Testing reachability integration with all rule types...');
const generator = new BigGraphGenerator();
const graphData = generator.generateGraph('enterprise');
const arbiter = generator.loadIntoArbiter(graphData);
// Initialize reachability checker
await arbiter.initializeReachabilityChecker({
strategy: 'auto',
twoHopOptions: { remainderBits: 8 },
treeCoverOptions: { remainderBits: 8 }
});
if (process.env.TEST_DEBUG === '1') console.log(' ✅ Reachability checker initialized');
// Test with different rule types
const ruleTypes = [
{ name: 'ChainRule', rule: new ChainRule(arbiter) },
{ name: 'DirectRule', rule: new DirectRule(arbiter) },
{ name: 'MultiHopRule', rule: new MultiHopRule(arbiter) }
];
const testRelations = graphData.relations.filter(r =>
r.src.startsWith('user:') && r.dst.startsWith('doc:')
).slice(0, 3);
for (const { name, rule } of ruleTypes) {
if (process.env.TEST_DEBUG === '1') console.log(` Testing ${name} reachability integration...`);
// Test quick reachability check
const sourceKey = testRelations[0].src;
const targetKey = testRelations[0].dst;
// Test _quickReachabilityCheck
const isReachable = rule._quickReachabilityCheck(sourceKey, targetKey);
if (process.env.TEST_DEBUG === '1') console.log(` ${name} quick check: ${sourceKey} -> ${targetKey}: ${isReachable}`);
assert.ok(typeof isReachable === 'boolean' || isReachable === null, 'Should return boolean or null');
// Test _quickReachabilityFailure
const failureResult = rule._quickReachabilityFailure(sourceKey, targetKey, `Test failure for ${name}`);
if (failureResult) {
if (process.env.TEST_DEBUG === '1') console.log(` ${name} quick failure: ${failureResult.possibility} (${failureResult.reason})`);
assert.ok(failureResult.possibility === 0, 'Quick failure should return 0 possibility');
assert.ok(failureResult.reason.includes(name), 'Should include rule name in reason');
} else {
if (process.env.TEST_DEBUG === '1') console.log(` ${name} quick failure: No failure (reachable or no checker)`);
}
// Test _getReachableNodes
const reachableNodes = rule._getReachableNodes(sourceKey, 5);
if (reachableNodes) {
if (process.env.TEST_DEBUG === '1') console.log(` ${name} reachable nodes: ${reachableNodes.length} found`);
assert.ok(Array.isArray(reachableNodes), 'Should return array of reachable nodes');
} else {
if (process.env.TEST_DEBUG === '1') console.log(` ${name} reachable nodes: No checker available`);
}
// Test _getReachingNodes
const reachingNodes = rule._getReachingNodes(targetKey, 5);
if (reachingNodes) {
if (process.env.TEST_DEBUG === '1') console.log(` ${name} reaching nodes: ${reachingNodes.length} found`);
assert.ok(Array.isArray(reachingNodes), 'Should return array of reaching nodes');
} else {
if (process.env.TEST_DEBUG === '1') console.log(` ${name} reaching nodes: No checker available`);
}
if (process.env.TEST_DEBUG === '1') console.log(`${name} reachability integration working`);
}
if (process.env.TEST_DEBUG === '1') console.log(' ✅ All rule types can use reachability failure checks');
});
test('verifies batch reachability checking', async () => {
if (process.env.TEST_DEBUG === '1') console.log('📦 Testing batch reachability checking...');
const generator = new BigGraphGenerator();
const graphData = generator.generateGraph('enterprise');
const arbiter = generator.loadIntoArbiter(graphData);
// Initialize reachability checker
await arbiter.initializeReachabilityChecker({
strategy: 'auto',
twoHopOptions: { remainderBits: 8 },
treeCoverOptions: { remainderBits: 8 }
});
const rule = new ChainRule(arbiter);
// Test batch reachability check
const testPairs = graphData.relations.filter(r =>
r.src.startsWith('user:') && r.dst.startsWith('doc:')
).slice(0, 5).map(r => ({ sourceKey: r.src, targetKey: r.dst }));
if (process.env.TEST_DEBUG === '1') console.log(` Testing batch check for ${testPairs.length} pairs...`);
const batchResults = rule._batchReachabilityCheck(testPairs);
if (batchResults) {
if (process.env.TEST_DEBUG === '1') console.log(` Batch results: ${Object.keys(batchResults).length} pairs checked`);
for (const [pair, result] of Object.entries(batchResults)) {
if (process.env.TEST_DEBUG === '1') console.log(` ${pair}: ${result}`);
assert.ok(typeof result === 'boolean' || result === null, 'Should return boolean or null');
}
if (process.env.TEST_DEBUG === '1') console.log(' ✅ Batch reachability checking working');
} else {
if (process.env.TEST_DEBUG === '1') console.log(' 📝 No reachability checker available for batch checking');
}
});
test('verifies reachability integration in rule evaluation', async () => {
if (process.env.TEST_DEBUG === '1') console.log('⚡ Testing reachability integration in actual rule evaluation...');
const generator = new BigGraphGenerator();
const graphData = generator.generateGraph('enterprise');
const arbiter = generator.loadIntoArbiter(graphData);
// Initialize reachability checker
await arbiter.initializeReachabilityChecker({
strategy: 'auto',
twoHopOptions: { remainderBits: 8 },
treeCoverOptions: { remainderBits: 8 }
});
const rule = new ChainRule(arbiter);
// Test actual rule evaluation with reachability
const testRelation = graphData.relations.find(r =>
r.src.startsWith('user:') && r.dst.startsWith('doc:')
);
if (testRelation) {
if (process.env.TEST_DEBUG === '1') console.log(` Testing rule evaluation: ${testRelation.src} -> ${testRelation.dst}`);
// Create a simple chain rule configuration
const ruleConfig = {
type: 'chain',
relations: ['role_membership', 'role_permission'],
maxSteps: 3
};
// Test rule evaluation
const result = rule.evaluate(
arbiter.nodeIdByKey.get(testRelation.src),
testRelation.src,
arbiter.nodeIdByKey.get(testRelation.dst),
testRelation.dst,
ruleConfig,
new Set(),
'can_read_via_role',
{}
);
if (process.env.TEST_DEBUG === '1') console.log(` Rule evaluation result: ${result.possibility} (${result.reason})`);
if (process.env.TEST_DEBUG === '1') console.log(` Meta method: ${result.meta?.method || 'unknown'}`);
assert.ok(typeof result.possibility === 'number', 'Should return numeric possibility');
assert.ok(result.reason, 'Should have a reason');
if (process.env.TEST_DEBUG === '1') console.log(' ✅ Rule evaluation with reachability integration working');
} else {
if (process.env.TEST_DEBUG === '1') console.log(' 📝 No suitable test relation found');
}
});