Files
core/tests/engine/fast-check-multi-hop.test.js
T

89 lines
2.9 KiB
JavaScript
Raw Normal View History

import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import fc from 'fast-check';
import { Arbiter } from '../../src/core/Arbiter.js';
function hasPathWithin(adj, src, dst, maxDepth) {
const visited = new Set([src]);
let frontier = [src];
let depth = 0;
while (frontier.length && depth < maxDepth) {
const next = [];
for (const node of frontier) {
const neighbors = adj.get(node) || [];
for (const n of neighbors) {
if (n === dst) return true;
if (!visited.has(n)) {
visited.add(n);
next.push(n);
}
}
}
frontier = next;
depth++;
}
return false;
}
describe('Fast-check: multi-hop invariants', () => {
test('zero-hop is denied unless explicitly enabled', () => {
const arbiter = new Arbiter();
arbiter.addNode('node:0', 'node');
arbiter.setRelationConfig('link', { type: 'direct' });
arbiter.setRelationConfig('reachable', { type: 'multi_hop', relation: 'link', maxDepth: 2 });
const defaultResult = arbiter.check('node:0', 'reachable', 'node:0');
assert.strictEqual(defaultResult.possibility > 0, false);
arbiter.setRelationConfig('reachable_allow', {
type: 'multi_hop',
relation: 'link',
maxDepth: 2,
allowZeroHop: true
});
const allowedResult = arbiter.check('node:0', 'reachable_allow', 'node:0');
assert.strictEqual(allowedResult.possibility > 0, true);
});
test('multi-hop reachability matches bounded BFS', () => {
fc.assert(
fc.property(
fc.integer({ min: 2, max: 6 }),
fc.integer({ min: 1, max: 4 }),
fc.array(
fc.record({
src: fc.integer({ min: 0, max: 5 }),
dst: fc.integer({ min: 0, max: 5 })
}),
{ minLength: 1, maxLength: 20 }
),
(nodeCount, maxDepth, edges) => {
const arbiter = new Arbiter();
for (let i = 0; i < nodeCount; i++) arbiter.addNode(`node:${i}`, 'node');
arbiter.setRelationConfig('link', { type: 'direct' });
arbiter.setRelationConfig('reachable', { type: 'multi_hop', relation: 'link', maxDepth, allowZeroHop: false });
const adj = new Map();
for (const edge of edges) {
const src = edge.src % nodeCount;
const dst = edge.dst % nodeCount;
arbiter.addRelation(`node:${src}`, 'link', `node:${dst}`, 1.0);
const list = adj.get(src) || [];
list.push(dst);
adj.set(src, list);
}
for (let i = 0; i < nodeCount; i++) {
for (let j = 0; j < nodeCount; j++) {
const expected = hasPathWithin(adj, i, j, maxDepth);
const result = arbiter.check(`node:${i}`, 'reachable', `node:${j}`);
assert.strictEqual(result.possibility > 0, expected);
}
}
}
),
{ numRuns: 30 }
);
});
});