ae21605fb7
The Evidence DSL (ADR-000) is a thin declarative layer that compiles to engine rule types. It has zero runtime coupling to the core engine (DSLCompiler takes an arbiter as a duck-typed argument; the only shared code was the ip-utils helpers, now local). Extracting it into its own package keeps the core artifact free of the DSL surface. - @arbiter/evidence-dsl depends on @arbiter/core (config formats are the compilation target) - deep-path exports for the compiler, parser, generator, validation, and built-in functions (the surface the core's DSL tests consume) - tests moved alongside; generate-parser script + peggy devDep local - CI: test on push, publish on v* tags
102 lines
2.8 KiB
JavaScript
102 lines
2.8 KiB
JavaScript
import { describe, test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { PeggyDSLParser } from '../src/parser/PeggyDSLParser.js';
|
|
|
|
describe('Peggy DSL Parser', () => {
|
|
const parser = new PeggyDSLParser();
|
|
|
|
test('Basic parsing', () => {
|
|
const dsl = `
|
|
definition User {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasRole(user: User, role: string)
|
|
|
|
evidence canRead(user: User, doc: Document) {
|
|
hasRole(user, 'admin')
|
|
}
|
|
`;
|
|
|
|
const program = parser.parse(dsl);
|
|
assert.ok(program !== null, 'Program should be created');
|
|
assert.ok(program.definitions.length === 1, 'Should have 1 definition');
|
|
assert.ok(program.facts.length === 1, 'Should have 1 fact');
|
|
assert.ok(program.evidence.length === 1, 'Should have 1 evidence');
|
|
});
|
|
|
|
test('Complex DSL parsing', () => {
|
|
const dsl = `
|
|
definition User {
|
|
role: string
|
|
isActive: boolean
|
|
clearance: string BEHAVES {
|
|
blurring adaptive confidence_95
|
|
} CACHE eager
|
|
}
|
|
|
|
fact hasRole(user: User, role: string) CACHE eager
|
|
fact isMember(user: User, group: Group) transitive CACHE lazy
|
|
|
|
evidence canRead(user: User, doc: Document) {
|
|
hasRole(user, 'admin')
|
|
|
|
isMember(user, *group) {
|
|
canRead(group, doc)
|
|
} limit 5
|
|
|
|
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
|
}
|
|
`;
|
|
|
|
const program = parser.parse(dsl);
|
|
assert.ok(program !== null, 'Program should be created');
|
|
assert.ok(program.definitions.length === 1, 'Should have 1 definition');
|
|
assert.ok(program.facts.length === 2, 'Should have 2 facts');
|
|
assert.ok(program.evidence.length === 1, 'Should have 1 evidence');
|
|
});
|
|
|
|
test('Error handling', () => {
|
|
const invalidDSL = `
|
|
definition User {
|
|
role: string
|
|
// Missing closing brace
|
|
|
|
fact hasRole(user: User, role: string)
|
|
// Missing semicolon
|
|
`;
|
|
|
|
assert.throws(
|
|
() => parser.parse(invalidDSL),
|
|
/Parsing failed/,
|
|
'Should have parsing error message'
|
|
);
|
|
});
|
|
|
|
test('Validation', () => {
|
|
const validDSL = `
|
|
definition User {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasRole(user: User, role: string)
|
|
`;
|
|
|
|
const invalidDSL = `
|
|
definition User {
|
|
role: string
|
|
// Missing closing brace
|
|
`;
|
|
|
|
const validResult = parser.validate(validDSL);
|
|
assert.ok(validResult.success, 'Valid DSL should pass validation');
|
|
assert.ok(validResult.program !== null, 'Valid DSL should return program');
|
|
|
|
const invalidResult = parser.validate(invalidDSL);
|
|
assert.ok(!invalidResult.success, 'Invalid DSL should fail validation');
|
|
assert.ok(invalidResult.errors.length > 0, 'Should have validation errors');
|
|
});
|
|
});
|