717ae1031e
Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/ binary modes, condensed snapshots, value relations) with 39 rigor test campaigns. Includes fixes for snapshot binary writer/reader format mismatch (snapshot-of-snapshot corruption), possibility write-boundary validation, empty-graph snapshot serialization, relation lookup cache direction collision, config-redefinition cache invalidation, binary threshold semantics, defeasible compiled routing, and comparator reason whitelisting.
266 lines
7.8 KiB
JavaScript
266 lines
7.8 KiB
JavaScript
import { describe, test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { DSLCompiler } from '../../src/ast/DSLCompiler.js';
|
|
import { parse } from '../../src/ast/parser/GeneratedParser.js';
|
|
import { RuleGenerator } from '../../src/ast/generator/RuleGenerator.js';
|
|
|
|
function createMockArbiter() {
|
|
const relationConfigs = new Map();
|
|
return {
|
|
relationConfigs,
|
|
setRelationConfig(relation, config) {
|
|
relationConfigs.set(relation, config);
|
|
}
|
|
};
|
|
}
|
|
|
|
describe('DSL Compiler', () => {
|
|
const arbiter = createMockArbiter();
|
|
const compiler = new DSLCompiler(arbiter);
|
|
|
|
test('Basic parsing', () => {
|
|
const dsl = `
|
|
definition Employee {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasRole(user: Employee, role: string)
|
|
|
|
evidence canRead(user: Employee, doc: Account) {
|
|
hasRole(user, 'admin')
|
|
}
|
|
`;
|
|
|
|
const result = compiler.compile(dsl, 'test-basic');
|
|
assert.ok(result.success, 'Basic parsing should succeed');
|
|
assert.ok(result.program !== null, 'Program should be created');
|
|
assert.ok(result.generatedRules.size > 0, 'Rules should be generated');
|
|
});
|
|
|
|
test('Basic compilation', () => {
|
|
const dsl = `
|
|
definition Employee {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasRole(user: Employee, role: string) CACHE eager
|
|
|
|
evidence canRead(user: Employee, doc: Account) {
|
|
hasRole(user, 'admin')
|
|
}
|
|
`;
|
|
|
|
const result = compiler.compile(dsl, 'test-compilation');
|
|
assert.ok(result.success, 'Basic compilation should succeed');
|
|
assert.ok(result.generatedRules.has('canRead'), 'canRead rule should be generated');
|
|
|
|
const canReadConfig = result.generatedRules.get('canRead');
|
|
assert.ok(canReadConfig.type === 'direct', 'canRead should be direct rule');
|
|
assert.ok(canReadConfig.relation === 'hasRole', 'canRead should use hasRole relation');
|
|
});
|
|
|
|
test('Complex DSL compilation', () => {
|
|
const dsl = `
|
|
definition Employee {
|
|
role: string
|
|
isActive: boolean
|
|
clearance: string BEHAVES {
|
|
blurring adaptive confidence_95
|
|
} CACHE eager
|
|
}
|
|
|
|
fact hasRole(user: Employee, role: string) CACHE eager
|
|
fact isMember(user: Employee, group: Device) transitive CACHE lazy
|
|
fact owns(user: Employee, doc: Account) CACHE eager
|
|
|
|
evidence canRead(user: Employee, doc: Account) {
|
|
owns(user, doc)
|
|
hasRole(user, 'admin')
|
|
}
|
|
|
|
evidence canAccessCritical(user: Employee, resource: AuthSession) {
|
|
fusion min {
|
|
hasRole(user, 'admin'),
|
|
hasRole(user, 'superadmin')
|
|
}
|
|
|
|
fusion max {
|
|
hasRole(user, 'admin'),
|
|
hasRole(user, 'secret')
|
|
}
|
|
}
|
|
`;
|
|
|
|
const result = compiler.compile(dsl, 'test-complex');
|
|
assert.ok(result.success, 'Complex DSL compilation should succeed');
|
|
assert.ok(result.generatedRules.has('canRead'), 'canRead rule should be generated');
|
|
assert.ok(result.generatedRules.has('canAccessCritical'), 'canAccessCritical rule should be generated');
|
|
|
|
const canReadConfig = result.generatedRules.get('canRead');
|
|
assert.ok(canReadConfig.type === 'logical', 'canRead should be logical rule');
|
|
});
|
|
|
|
test('Error handling', () => {
|
|
const invalidDSL = `
|
|
definition Employee {
|
|
role: string
|
|
// Missing closing brace
|
|
|
|
fact hasRole(user: Employee, role: string)
|
|
// Missing semicolon
|
|
|
|
evidence canRead(user: Employee, doc: Account) {
|
|
// Invalid syntax
|
|
invalid syntax here
|
|
}
|
|
`;
|
|
|
|
const result = compiler.compile(invalidDSL, 'test-error');
|
|
assert.ok(!result.success, 'Invalid DSL should fail');
|
|
assert.ok(result.errors.length > 0, 'Should have error messages');
|
|
});
|
|
|
|
test('Program management', () => {
|
|
const dsl1 = `
|
|
definition Employee {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasRole(user: Employee, role: string)
|
|
evidence canRead(user: Employee, doc: Account) {
|
|
hasRole(user, 'admin')
|
|
}
|
|
`;
|
|
|
|
const dsl2 = `
|
|
definition Employee {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasBalance(user: Employee, amount: number)
|
|
evidence canWithdraw(user: Employee, amount: number) {
|
|
hasBalance(user, amount)
|
|
}
|
|
`;
|
|
|
|
const result1 = compiler.compile(dsl1, 'test-auth');
|
|
assert.ok(result1.success, 'First program should compile');
|
|
|
|
const programs = { 'auth': dsl1, 'finance': dsl2 };
|
|
const result2 = compiler.compileMultiple(programs);
|
|
assert.ok(result2.success, 'Multiple programs should compile');
|
|
|
|
const authProgram = compiler.getCompiledProgram('test-auth');
|
|
assert.ok(authProgram !== null, 'Should retrieve compiled program');
|
|
|
|
const removed = compiler.removeCompiledProgram('test-auth');
|
|
assert.ok(removed, 'Should remove program');
|
|
|
|
compiler.clearCompiledPrograms();
|
|
const allPrograms = compiler.getAllCompiledPrograms();
|
|
assert.ok(allPrograms.size === 0, 'Should clear all programs');
|
|
});
|
|
|
|
test('Validation', () => {
|
|
const validDSL = `
|
|
definition Employee {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasRole(user: Employee, role: string)
|
|
|
|
evidence canRead(user: Employee, doc: Account) {
|
|
hasRole(user, 'admin')
|
|
}
|
|
`;
|
|
|
|
const invalidDSL = `
|
|
definition Employee {
|
|
role: string
|
|
// Missing closing brace
|
|
|
|
fact hasRole(user: Employee, role: string)
|
|
// Missing semicolon
|
|
`;
|
|
|
|
const validResult = compiler.validate(validDSL);
|
|
assert.ok(validResult.success, 'Valid DSL should pass validation');
|
|
|
|
const invalidResult = compiler.validate(invalidDSL);
|
|
assert.ok(!invalidResult.success, 'Invalid DSL should fail validation');
|
|
assert.ok(invalidResult.errors.length > 0, 'Should have validation errors');
|
|
});
|
|
|
|
test('Rule generation', () => {
|
|
const dsl = `
|
|
definition Employee {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasRole(user: Employee, role: string) CACHE eager
|
|
fact isMember(user: Employee, group: Device) transitive CACHE lazy
|
|
|
|
evidence canRead(user: Employee, doc: Account) {
|
|
hasRole(user, 'admin')
|
|
}
|
|
|
|
evidence canAccess(user: Employee, doc: Account) {
|
|
hasRole(user, 'reader')
|
|
}
|
|
`;
|
|
|
|
const result = compiler.compile(dsl, 'test-rules');
|
|
assert.ok(result.success, 'Rule generation should succeed');
|
|
|
|
const canReadConfig = result.generatedRules.get('canRead');
|
|
assert.ok(canReadConfig.type === 'direct', 'canRead should be direct rule');
|
|
|
|
const canAccessConfig = result.generatedRules.get('canAccess');
|
|
assert.ok(canAccessConfig.type === 'direct', 'canAccess should be direct rule');
|
|
});
|
|
|
|
test('Multiple programs', () => {
|
|
const programs = {
|
|
'auth': `
|
|
definition Employee {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasRole(user: Employee, role: string)
|
|
evidence canRead(user: Employee, doc: Account) {
|
|
hasRole(user, 'admin')
|
|
}
|
|
`,
|
|
'finance': `
|
|
definition Employee {
|
|
role: string
|
|
isActive: boolean
|
|
}
|
|
|
|
fact hasBalance(user: Employee, amount: number)
|
|
evidence canWithdraw(user: Employee, amount: number) {
|
|
hasBalance(user, amount)
|
|
}
|
|
`,
|
|
'invalid': `
|
|
// Invalid syntax
|
|
invalid syntax here
|
|
`
|
|
};
|
|
|
|
const result = compiler.compileMultiple(programs);
|
|
assert.ok(!result.success, 'Should fail due to invalid program');
|
|
assert.ok(result.errors.length > 0, 'Should have errors');
|
|
assert.ok(result.results.auth.success, 'Auth program should succeed');
|
|
assert.ok(result.results.finance.success, 'Finance program should succeed');
|
|
assert.ok(!result.results.invalid.success, 'Invalid program should fail');
|
|
});
|
|
});
|