124 lines
4.8 KiB
JavaScript
124 lines
4.8 KiB
JavaScript
|
|
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);
|
||
|
|
});
|
||
|
|
});
|