import { test, describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { setupChainRuleTestGraph } from './helpers.js'; describe('ChainRule Comprehensive Tests', () => { describe('Fast Path and Performance Optimization', () => { it('supports early exit with minAllowPossibility threshold', () => { const arbiter = setupChainRuleTestGraph(); arbiter.setRelationConfig('quick_access_check', { type: 'chain', steps: [ { relation: 'member_of', direction: 'out' }, { relation: 'manages', direction: 'out' } ] }); // Test with fast path enabled and low threshold const result = arbiter.check('user:alice', 'quick_access_check', 'project:web-app', { fastPath: true, minAllowPossibility: 0.5 // Exit early if possibility >= 0.5 }); assert.equal(result.possibility, 1); assert.equal(result.reason, 'allow_threshold_met'); }); it('supports early exit with maxDenyPossibility threshold', () => { const arbiter = setupChainRuleTestGraph(); arbiter.setRelationConfig('deny_check', { type: 'chain', steps: [ { relation: 'member_of', direction: 'out' }, { relation: 'manages', direction: 'out' } ] }); // Test denial with fast path const result = arbiter.check('user:alice', 'deny_check', 'project:mobile-app', { fastPath: true, maxDenyPossibility: 0.8 // Exit early if denial >= 0.8 }); assert.equal(result.possibility, 0); assert.equal(result.reason, 'no_chain_path_found'); }); }); describe('Comparison with Other Rules', () => { it('compares ChainRule vs ParentRule performance', () => { const arbiter = setupChainRuleTestGraph(); // Set up equivalent rules using ChainRule and ParentRule arbiter.setRelationConfig('chain_parent_check', { type: 'chain', steps: [ { relation: 'manages', direction: 'in' }, // project ← group { relation: 'member_of', direction: 'in' } // group ← user ] }); arbiter.setRelationConfig('traditional_parent_check', { type: 'parent', parentRelation: 'manages', relation: 'member_of', reverse: true }); const startTime1 = Date.now(); const chainResult = arbiter.check('project:web-app', 'chain_parent_check', 'user:alice'); const chainTime = Date.now() - startTime1; const startTime2 = Date.now(); const parentResult = arbiter.check('project:web-app', 'traditional_parent_check', 'user:alice'); const parentTime = Date.now() - startTime2; if (process.env.TEST_DEBUG === '1') console.log('ChainRule result:', chainResult.possibility); if (process.env.TEST_DEBUG === '1') console.log('ParentRule result:', parentResult.possibility); // ChainRule should succeed (Alice is member of engineering, which manages web-app) assert.equal(chainResult.possibility, 1); // ParentRule might have different semantics - just verify it returns a valid result assert.ok(typeof parentResult.possibility === 'number'); if (process.env.TEST_DEBUG === '1') console.log(`ChainRule time: ${chainTime}ms, ParentRule time: ${parentTime}ms`); // ChainRule should be reasonably performant assert.ok(chainTime < 100); // Should complete in reasonable time }); it('compares ChainRule vs MultiHopRule for path finding', () => { const arbiter = setupChainRuleTestGraph(); // ChainRule: specific path arbiter.setRelationConfig('chain_access', { type: 'chain', steps: [ { relation: 'member_of', direction: 'out' }, { relation: 'belongs_to', direction: 'out' }, { relation: 'has_access_level', direction: 'out' } ] }); // MultiHopRule: flexible path finding arbiter.setRelationConfig('multihop_access', { type: 'multi_hop', relation: 'member_of', maxDepth: 3, pathAggregation: 'max' }); const chainResult = arbiter.check('user:alice', 'chain_access', 'access:level-5'); const multihopResult = arbiter.check('user:alice', 'multihop_access', 'access:level-5'); // ChainRule should give precise result for defined path assert.equal(chainResult.possibility, 1); // MultiHopRule might give different result based on path exploration if (process.env.TEST_DEBUG === '1') console.log('Chain vs MultiHop results:', { chain: chainResult.possibility, multihop: multihopResult.possibility }); }); it('demonstrates ChainRule semantic clarity vs other approaches', () => { const arbiter = setupChainRuleTestGraph(); // ChainRule: explicit semantic path arbiter.setRelationConfig('semantic_chain', { type: 'chain', steps: [ { relation: 'member_of', direction: 'out' }, { relation: 'manages', direction: 'out' }, { relation: 'has_budget', direction: 'out' } ], extractValues: true, extractFrom: 2, extractRelation: 'has_budget', valueAggregation: 'sum' }); const result = arbiter.check('user:alice', 'semantic_chain', 'budget:tech-2024'); // Should provide clear semantic meaning: // "user's budget access through group project management" assert.equal(result.possibility, 1); // Values are internal only, not exposed in authorization results assert.equal(result.value, undefined); if (process.env.TEST_DEBUG === '1') console.log('Semantic chain result:', { access: result.possibility, meaning: 'user → group → project → budget' }); }); }); });