import { ParentRule } from '../../src/authorization/rules/ParentRule.js'; import { Arbiter } from '../../src/core/Arbiter.js'; import { describe, it, beforeEach } from 'node:test'; import assert from 'node:assert/strict'; describe('ParentRule', () => { let arbiter; let parentRule; beforeEach(() => { // Minimal mock Arbiter with relationManager and inference arbiter = { relationManager: { getRelationsFromSrc: (id, rel) => { if (rel === 'parent' && id === 'child1') { return [{ src: 'child1', dst: 'parentA', rel: 'parent', possibility: 0.8 }]; } if (rel === 'parent' && id === 'child2') { return []; } return []; }, getRelationsToDst: (id, rel) => { if (rel === 'parent' && id === 'child1') { return [{ src: 'parentA', dst: 'child1', rel: 'parent', possibility: 0.8 }]; } if (rel === 'parent' && id === 'child2') { return []; } return []; }, getRelationsByName: (rel) => { if (rel === 'parent') { return [ { src: 'parentA', dst: 'child1', rel: 'parent', possibility: 0.8 }, { src: 'parentB', dst: 'child2', rel: 'parent', possibility: 0.4 } ]; } return []; } }, keyByNodeId: new Map([ ['parentA', 'parentA'], ['parentB', 'parentB'], ['child1', 'child1'], ['child2', 'child2'] ]), resolveKey: (id) => arbiter.keyByNodeId.get(id), indices: { getDirectRelation: (srcId, rel, dstId) => { if (srcId === 'user' && dstId === 'parentA') return { possibility: 0.8 }; if (srcId === 'user' && dstId === 'parentB') return { possibility: 0.6 }; return null; } }, _getInferenceEngine: () => ({ params: { minCaseThreshold: 1 }, estimatePolicyElement: (src, rel, dst) => { if (src === 'parentB' && rel === 'parent' && dst === 'child2') { return { outcome: 'positive', totalCases: 2, possibility: 0.6 }; } return { outcome: 'negative', totalCases: 1, possibility: 0.1 }; } }) }; parentRule = new ParentRule(arbiter); }); it('returns correct possibility and meta for direct parent', () => { const rule = { type: 'parent', parentRelation: 'parent' }; const res = parentRule._evaluateRule('user', 'user', 'child1', 'child1', rule, {}, null, {}); assert.strictEqual(res.possibility, 0.8); assert.ok(res.meta.parentRule); assert.strictEqual(res.meta.parentRule.type, 'parent_access_checked'); assert.strictEqual(res.meta.parentRule.parentKey, 'parentA'); }); // it('returns correct possibility and meta for inferred parent', () => { // // Mock inference result // arbiter.inferencer = { // inferRelation: () => ({ // possibility: 0.6, // Inferred possibility // meta: { type: 'inferred', reliability: 0.8 } // }) // }; // const res = parentRule._evaluateRule('child1', 'child1', 'grandparent', 'grandparent', ruleInferred, {}, null, {}); // assert.ok(res.possibility > 0.5 && res.possibility < 0.7); // Check within inferred range // assert.deepStrictEqual(res.meta.type, 'inferred'); // }); it('returns 0 possibility if no parent and no inference', () => { const rule = { type: 'parent', parentRelation: 'parent', allowInference: false }; const res = parentRule._evaluateRule('user', 'user', 'child2', 'child2', rule, {}, null, {}); assert.strictEqual(res.possibility, 0); assert.ok(res.meta.parentRule); assert.strictEqual(res.meta.parentRule.type, 'no_parent_relationship_found'); }); it('applies minPossibility threshold', () => { const rule = { type: 'parent', parentRelation: 'parent' }; const res = parentRule._evaluateRule('user', 'user', 'child1', 'child1', rule, {}, null, { minPossibility: 0.9, fastPath: true }); assert.strictEqual(res.possibility, 0); assert.ok(res.meta.parentRule.cutoffAppliedPostFusion); }); it('applies OWA aggregation for multiple parents', () => { // Add a second direct parent for child1 arbiter.relationManager.getRelationsToDst = (id, rel) => { if (rel === 'parent' && id === 'child1') { return [ { src: 'parentA', dst: 'child1', rel: 'parent', possibility: 0.8 }, { src: 'parentB', dst: 'child1', rel: 'parent', possibility: 0.6 } ]; } return []; }; const rule = { type: 'parent', parentRelation: 'parent', aggregator: 'mean' }; const res = parentRule._evaluateRule('user', 'user', 'child1', 'child1', rule, {}, null, {}); assert.ok(res.possibility > 0.6 && res.possibility < 0.8); assert.ok(res.meta.parentRule.fusionMethod === 'mean'); assert.strictEqual(res.meta.parentRule.pathsConsidered, 2); }); });