108 lines
4.6 KiB
JavaScript
108 lines
4.6 KiB
JavaScript
|
|
/**
|
||
|
|
* SnapshotBinary round-trip tests.
|
||
|
|
*
|
||
|
|
* Verifies:
|
||
|
|
* - serialize/deserialize preserves nodes and relations
|
||
|
|
* - Deserialized snapshots are marked _snapshotReadOnly
|
||
|
|
* - UTF-8 keys survive round-trip
|
||
|
|
* - Relation configs survive round-trip
|
||
|
|
*
|
||
|
|
* Run: node --test --test-force-exit lib/tests/property-based/snapshot-binary.test.js
|
||
|
|
*/
|
||
|
|
import { Arbiter } from '../../src/core/Arbiter.js';
|
||
|
|
import { describe, it } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
|
||
|
|
function createArbiterFactory() {
|
||
|
|
return () => new Arbiter({ fastConstructionMode: true, enableInference: false });
|
||
|
|
}
|
||
|
|
|
||
|
|
async function loadSnapshotModule() {
|
||
|
|
return import('../../src/core/SnapshotBinary.js');
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('SnapshotBinary — Round-Trip', () => {
|
||
|
|
it('serialize and deserialize preserves nodes and relations', async () => {
|
||
|
|
const { serializeArbiterSnapshot, deserializeArbiterSnapshot } = await loadSnapshotModule();
|
||
|
|
|
||
|
|
const original = new Arbiter({ fastConstructionMode: true, enableInference: false });
|
||
|
|
original.addNode('user:alice', 'user');
|
||
|
|
original.addNode('project:secret', 'project');
|
||
|
|
original.addRelation('user:alice', 'can_read', 'project:secret', { possibility: 0.9, value: 100 });
|
||
|
|
original.setRelationConfig('can_read', { type: 'direct' });
|
||
|
|
original.enableCondensedSnapshot();
|
||
|
|
|
||
|
|
const buffer = serializeArbiterSnapshot(original);
|
||
|
|
assert.ok(buffer instanceof ArrayBuffer);
|
||
|
|
assert.ok(buffer.byteLength > 0);
|
||
|
|
|
||
|
|
const restored = deserializeArbiterSnapshot(buffer, createArbiterFactory());
|
||
|
|
assert.ok(restored.nodeIdByKey.has('user:alice'));
|
||
|
|
assert.ok(restored.nodeIdByKey.has('project:secret'));
|
||
|
|
|
||
|
|
const nodes = [...restored.nodes.values()].filter(Boolean);
|
||
|
|
assert.ok(nodes.length >= 2);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('deserialized snapshot is marked read-only', async () => {
|
||
|
|
const { serializeArbiterSnapshot, deserializeArbiterSnapshot } = await loadSnapshotModule();
|
||
|
|
|
||
|
|
const original = new Arbiter({ fastConstructionMode: true, enableInference: false });
|
||
|
|
original.addNode('user:alice', 'user');
|
||
|
|
original.addNode('project:secret', 'project');
|
||
|
|
original.addRelation('user:alice', 'can_read', 'project:secret', { possibility: 0.9 });
|
||
|
|
original.enableCondensedSnapshot();
|
||
|
|
|
||
|
|
const buffer = serializeArbiterSnapshot(original);
|
||
|
|
const restored = deserializeArbiterSnapshot(buffer, createArbiterFactory());
|
||
|
|
|
||
|
|
assert.strictEqual(restored._snapshotReadOnly, true);
|
||
|
|
assert.strictEqual(restored.snapshotEnabled, true);
|
||
|
|
assert.ok(restored.snapshotGraph);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('UTF-8 keys survive round-trip', async () => {
|
||
|
|
const { serializeArbiterSnapshot, deserializeArbiterSnapshot } = await loadSnapshotModule();
|
||
|
|
|
||
|
|
const original = new Arbiter({ fastConstructionMode: true, enableInference: false });
|
||
|
|
original.addNode('user:alice', 'user');
|
||
|
|
original.addNode('project:résumé', 'project');
|
||
|
|
original.addRelation('user:alice', 'can_read', 'project:résumé', { possibility: 0.9 });
|
||
|
|
original.setRelationConfig('can_read', { type: 'direct' });
|
||
|
|
original.enableCondensedSnapshot();
|
||
|
|
|
||
|
|
const buffer = serializeArbiterSnapshot(original);
|
||
|
|
const restored = deserializeArbiterSnapshot(buffer, createArbiterFactory());
|
||
|
|
|
||
|
|
assert.ok(restored.nodeIdByKey.has('project:résumé'));
|
||
|
|
assert.strictEqual(restored.relationConfigs.has('can_read'), true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('relation configs survive round-trip', async () => {
|
||
|
|
const { serializeArbiterSnapshot, deserializeArbiterSnapshot } = await loadSnapshotModule();
|
||
|
|
|
||
|
|
const original = new Arbiter({ fastConstructionMode: true, enableInference: false });
|
||
|
|
original.addNode('user:alice', 'user');
|
||
|
|
original.addNode('project:secret', 'project');
|
||
|
|
original.addRelation('user:alice', 'can_access', 'project:secret', {
|
||
|
|
possibility: 0.8,
|
||
|
|
value: 42,
|
||
|
|
reliability: 0.95
|
||
|
|
});
|
||
|
|
original.setRelationConfig('can_access', { type: 'chain', steps: [{ relation: 'parent', direction: 'out' }] });
|
||
|
|
original.setRelationConfig('can_read', { type: 'direct', minPossibility: 0.5 });
|
||
|
|
original.enableCondensedSnapshot();
|
||
|
|
|
||
|
|
const buffer = serializeArbiterSnapshot(original);
|
||
|
|
const restored = deserializeArbiterSnapshot(buffer, createArbiterFactory());
|
||
|
|
|
||
|
|
const accessConfig = restored.relationConfigs.get('can_access');
|
||
|
|
assert.strictEqual(accessConfig && accessConfig.type, 'chain');
|
||
|
|
assert.deepStrictEqual(accessConfig && accessConfig.steps, [{ relation: 'parent', direction: 'out' }]);
|
||
|
|
|
||
|
|
const readConfig = restored.relationConfigs.get('can_read');
|
||
|
|
assert.strictEqual(readConfig && readConfig.type, 'direct');
|
||
|
|
assert.strictEqual(readConfig && readConfig.minPossibility, 0.5);
|
||
|
|
});
|
||
|
|
});
|