import { describe, test } from 'node:test'; import assert from 'node:assert/strict'; import fc from 'fast-check'; import { Arbiter } from '../../src/core/Arbiter.js'; describe('Fast-check: defeasible unless invariants', () => { test('blocked edge defeats direct access', () => { fc.assert( fc.property( fc.integer({ min: 1, max: 5 }), fc.integer({ min: 1, max: 5 }), fc.array( fc.record({ user: fc.integer({ min: 0, max: 4 }), doc: fc.integer({ min: 0, max: 4 }) }), { minLength: 0, maxLength: 20 } ), fc.array( fc.record({ user: fc.integer({ min: 0, max: 4 }), doc: fc.integer({ min: 0, max: 4 }) }), { minLength: 0, maxLength: 20 } ), (userCount, docCount, viewers, blocked) => { const arbiter = new Arbiter(); for (let u = 0; u < userCount; u++) arbiter.addNode(`user:${u}`, 'user'); for (let d = 0; d < docCount; d++) arbiter.addNode(`doc:${d}`, 'doc'); arbiter.setRelationConfig('viewer', { type: 'direct' }); arbiter.setRelationConfig('blocked', { type: 'direct' }); arbiter.setRelationConfig('can_view', { when: { union: [ { type: 'direct', relation: 'viewer' } ] }, unless: { union: [ { type: 'direct', relation: 'blocked' } ] } }); const viewerSet = new Set(); const blockedSet = new Set(); for (const edge of viewers) { const u = edge.user % userCount; const d = edge.doc % docCount; arbiter.addRelation(`user:${u}`, 'viewer', `doc:${d}`, 1.0); viewerSet.add(`${u}:${d}`); } for (const edge of blocked) { const u = edge.user % userCount; const d = edge.doc % docCount; arbiter.addRelation(`user:${u}`, 'blocked', `doc:${d}`, 1.0); blockedSet.add(`${u}:${d}`); } for (let u = 0; u < userCount; u++) { for (let d = 0; d < docCount; d++) { const result = arbiter.check(`user:${u}`, 'can_view', `doc:${d}`); const key = `${u}:${d}`; const expected = viewerSet.has(key) && !blockedSet.has(key); assert.strictEqual(result.possibility > 0, expected); } } } ), { numRuns: 30 } ); }); });