import { describe, test } from 'node:test'; import assert from 'node:assert/strict'; import { DSLCompiler } from '../../src/ast/DSLCompiler.js'; function createMockArbiter() { const relationConfigs = new Map(); return { relationConfigs, setRelationConfig(relation, config) { relationConfigs.set(relation, config); } }; } describe('Evidence Rules', () => { const arbiter = createMockArbiter(); const compiler = new DSLCompiler(arbiter); test('Basic evidence', () => { const testCases = [ { input: `evidence canRead(user: User, doc: Document) { hasRole(user, 'admin') }`, description: 'Simple evidence with function call' }, { input: `evidence canAccess(user: User, resource: Resource) { user.isActive }`, description: 'Evidence with attribute access' }, { input: `evidence canModify(user: User, doc: Document) { user.isActive hasRole(user, 'admin') }`, description: 'Evidence with multiple conditions' }, { input: `evidence canDelete(user: User, doc: Document) { owns(user, doc) user.isActive }`, description: 'Evidence with ownership and status' } ]; testCases.forEach(({ input, description }) => { const result = compiler.compile(input, `test-basic-evidence-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); assert.ok(result.program.evidence.length > 0, 'Should have evidence'); }); }); test('Defeasible logic', () => { const testCases = [ { input: `evidence canAccess(user: User, resource: Resource) { ALWAYS user.isActive }`, description: 'ALWAYS rule - strict requirement' }, { input: `evidence canAccess(user: User, resource: Resource) { WHEN hasRole(user, 'admin') }`, description: 'WHEN rule - defeasible condition' }, { input: `evidence canAccess(user: User, resource: Resource) { WHEN hasRole(user, 'admin') UNLESS isSuspended(user) }`, description: 'WHEN/UNLESS rule - defeasible with defeater' }, { input: `evidence canAccess(user: User, resource: Resource) { REQUIRES hasClearance(user, resource.level) }`, description: 'REQUIRES rule - inverse defeater' }, { input: `evidence canAccessCritical(user: User, resource: Resource) { ALWAYS user.isActive WHEN hasRole(user, 'admin') UNLESS isSuspended(user) REQUIRES hasClearance(user, resource.level) }`, description: 'Complex defeasible logic with all rule types' }, { input: `evidence canAccessSensitive(user: User, doc: Document) { ALWAYS user.isActive WHEN hasRole(user, 'admin') UNLESS isSuspended(user) REQUIRES hasClearance(user, doc.level) fusion majority { user.isTrusted user.hasRecentActivity } }`, description: 'Defeasible logic with fusion' } ]; testCases.forEach(({ input, description }) => { const result = compiler.compile(input, `test-defeasible-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); test('Pattern matching', () => { const testCases = [ { input: `evidence canRead(user: User, doc: Document) { isMember(user, *group) { canRead(group, doc) } }`, description: 'Basic pattern matching with wildcard' }, { input: `evidence canRead(user: User, doc: Document) { isMember(user, *group) { canRead(group, doc) } limit 5 }`, description: 'Pattern matching with limit' }, { input: `evidence canRead(user: User, doc: Document) { similar(doc, *similar) |similarity| { canRead(user, similar) } with similarity > 0.7 }`, description: 'Pattern matching with binding and condition' }, { input: `evidence canRead(user: User, doc: Document) { similar(doc, *similar) |similarity| { canRead(user, similar) } with similarity > 0.7 limit 5 }`, description: 'Pattern matching with binding, condition, and limit' }, { input: `evidence canRead(user: User, doc: Document) { isMember(user, *group) { isMember(group, *parentGroup) { canRead(parentGroup, doc) } limit 2 } limit 3 }`, description: 'Nested pattern matching' }, { input: `evidence canRead(user: User, doc: Document) { isFriend(user, *friend) { isMember(friend, *group) { canRead(group, doc) } limit 1 } limit 5 }`, description: 'Multi-hop pattern matching' } ]; testCases.forEach(({ input, description }) => { const result = compiler.compile(input, `test-pattern-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); test('Fusion', () => { const testCases = [ { input: `evidence canAccess(user: User, resource: Resource) { fusion min { hasClearance(user, resource.level) user.isActive } }`, description: 'Min fusion - all conditions must be true' }, { input: `evidence canAccess(user: User, resource: Resource) { fusion max { hasRole(user, 'admin') hasRole(user, 'superuser') } }`, description: 'Max fusion - any condition can be true' }, { input: `evidence canAccess(user: User, resource: Resource) { fusion majority { hasClearance(user, 'secret') user.isTrusted user.hasRecentActivity } }`, description: 'Majority fusion - most conditions must be true' }, { input: `evidence canAccessCritical(user: User, resource: Resource) { fusion min { hasClearance(user, resource.level) user.isActive NOT user.isBlacklisted } fusion max { hasRole(user, 'admin') fusion majority { hasClearance(user, 'secret') user.isTrusted user.lastActive within 1hr } } }`, description: 'Nested fusion with different strategies' }, { input: `evidence canAccess(user: User, resource: Resource) { fusion average { user.reputation user.activityScore user.verificationLevel } }`, description: 'Average fusion for numeric values' } ]; testCases.forEach(({ input, description }) => { const result = compiler.compile(input, `test-fusion-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); test('Complex evidence', () => { const testCases = [ { input: `evidence canRead(user: User, doc: Document) { owns(user, doc) isMember(user, *group) { canRead(group, doc) } limit 5 parentOf(user, *parent) { canRead(parent, doc) } limit 3 similar(doc, *similar) |similarity| { canRead(user, similar) } with similarity > 0.7 limit 5 WHEN hasRole(user, 'admin') UNLESS isSuspended(user) }`, description: 'Complex evidence with all features' }, { input: `evidence canAccessCritical(user: User, resource: Resource) { ALWAYS user.isActive WHEN hasRole(user, 'admin') UNLESS isSuspended(user) REQUIRES hasClearance(user, resource.level) fusion min { hasClearance(user, resource.level) user.isActive NOT user.isBlacklisted } fusion max { hasRole(user, 'admin') fusion majority { hasClearance(user, 'secret') user.isTrusted user.lastActive within 1hr } } }`, description: 'Critical access with all rule types and fusion' }, { input: `evidence canModify(user: User, doc: Document) { owns(user, doc) isMember(user, *group) { canModify(group, doc) } limit 3 similar(doc, *similar) |similarity| { canModify(user, similar) similar.isEditable } with similarity > 0.8 limit 2 fusion majority { user.isTrusted user.hasRecentActivity doc.isPublic } }`, description: 'Modification access with similarity and fusion' } ]; testCases.forEach(({ input, description }) => { const result = compiler.compile(input, `test-complex-evidence-${Date.now()}`); assert.ok(result.success, `${description} should parse successfully`); }); }); test('Evidence error handling', () => { const testCases = [ { input: `evidence canRead(user: User, doc: Document) { hasRole(user, 'admin' }`, description: 'Missing closing parenthesis should fail' }, { input: `evidence canRead(user: User, doc: Document) { WHEN hasRole(user, 'admin') UNLESS }`, description: 'Incomplete UNLESS condition should fail' }, { input: `evidence canRead(user: User, doc: Document) { fusion min { hasRole(user, 'admin') }`, description: 'Incomplete fusion should fail' }, { input: `evidence canRead(user: User, doc: Document) { isMember(user, *group) { canRead(group, doc) } with }`, description: 'Incomplete with clause should fail' }, { input: `evidence canRead(user: User, doc: Document) { isMember(user, *group) { canRead(group, doc) } limit }`, description: 'Incomplete limit should fail' }, { input: `evidence canRead(user: User, doc: Document) { invalid syntax here }`, description: 'Invalid syntax should fail' } ]; testCases.forEach(({ input, description }) => { try { const result = compiler.compile(input, `test-evidence-error-${Date.now()}`); assert.ok(!result.success, `${description} should fail to parse`); } catch { // Expected to fail } }); }); });