import { describe, test } from 'node:test'; import assert from 'node:assert/strict'; import { DSLCompiler } from '../src/DSLCompiler.js'; import { parse } from '../src/parser/GeneratedParser.js'; import { RuleGenerator } from '../src/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('Rejects duplicate fields within a definition', () => { const dupFieldDSL = ` definition Employee { id: string id: string } `; const result = compiler.validate(dupFieldDSL); assert.ok(!result.success, 'Duplicate field should fail validation'); assert.match(result.errors[0], /Duplicate field 'Employee.id'/); }); test('Hints at the real constraint for common declaration mistakes', () => { const withinOnFact = compiler.compile(` definition Employee { id: string? } fact owns(user: Employee, doc: Employee) within 1h `, 'err-within'); assert.match(withinOnFact.errors[0], /within.*only valid on `source`/); const doubleBehaves = compiler.compile(` definition Employee { id: string? } fact rel(user: Employee, doc: Employee) BEHAVES AS transitive BEHAVES { ttl 1h } `, 'err-behaves'); assert.match(doubleBehaves.errors[0], /only one `BEHAVES` clause/); const limitAfterBody = compiler.compile(` definition Employee { id: string? } fact owns(user: Employee, doc: Employee) evidence can_read(user: Employee, doc: Employee) { owns(user, doc) } limit 5 `, 'err-limit'); assert.match(limitAfterBody.errors[0], /`limit` is only valid on pattern/); }); 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'); }); });