/** * Fact Declaration Tests * * Tests the fact declaration system of the Evidence DSL, * including properties, caching, limits, and parameter types. */ import { DSLCompiler } from '../DSLCompiler.js'; export class FactTests { constructor() { this.arbiter = null; this.compiler = null; this.testResults = []; } setup(arbiter) { this.arbiter = arbiter; this.compiler = new DSLCompiler(arbiter); } runAllTests() { console.log('=== Fact Declaration Tests ===\n'); this.testBasicFacts(); this.testFactProperties(); this.testFactCaching(); this.testFactLimits(); this.testParameterTypes(); this.testComplexFacts(); this.testFactErrors(); return this.getTestResults(); } testBasicFacts() { console.log('Testing Basic Facts...'); const testCases = [ { input: `fact hasRole(user: User, role: string)`, description: 'Simple fact with two parameters' }, { input: `fact isMember(user: User, group: Group)`, description: 'Fact with custom types' }, { input: `fact owns(user: User, doc: Document)`, description: 'Fact with multiple custom types' }, { input: `fact isActive(user: User)`, description: 'Fact with single parameter' }, { input: `fact hasPermission(user: User, resource: Resource, action: string)`, description: 'Fact with three parameters' } ]; testCases.forEach(({ input, description }) => { try { const result = this.compiler.compile(input, `test-basic-fact-${Date.now()}`); this.assert(result.success, `${description} should parse successfully`); this.assert(result.program.facts.length > 0, 'Should have facts'); console.log(` ✓ ${description}`); } catch (error) { this.fail(`Basic fact test: ${description}`, error); } }); } testFactProperties() { console.log('Testing Fact Properties...'); const testCases = [ { input: `fact isMember(user: User, group: Group) transitive`, description: 'Transitive fact' }, { input: `fact isFriend(user: User, friend: User) symmetrical`, description: 'Symmetrical fact' }, { input: `fact isMember(user: User, group: Group) transitive symmetrical`, description: 'Fact with multiple properties' }, { input: `fact isColleague(user: User, colleague: User) symmetrical`, description: 'Symmetrical relationship fact' }, { input: `fact isParentOf(parent: User, child: User) transitive`, description: 'Transitive hierarchical fact' } ]; testCases.forEach(({ input, description }) => { try { const result = this.compiler.compile(input, `test-fact-property-${Date.now()}`); this.assert(result.success, `${description} should parse successfully`); console.log(` ✓ ${description}`); } catch (error) { this.fail(`Fact property test: ${description}`, error); } }); } testFactCaching() { console.log('Testing Fact Caching...'); const testCases = [ { input: `fact hasRole(user: User, role: string) CACHE eager`, description: 'Eager cached fact' }, { input: `fact isMember(user: User, group: Group) CACHE lazy`, description: 'Lazy cached fact' }, { input: `fact isMember(user: User, group: Group) transitive CACHE eager`, description: 'Transitive fact with eager caching' }, { input: `fact isFriend(user: User, friend: User) symmetrical CACHE lazy`, description: 'Symmetrical fact with lazy caching' }, { input: `fact hasPermission(user: User, resource: Resource, action: string) CACHE eager`, description: 'Multi-parameter fact with eager caching' } ]; testCases.forEach(({ input, description }) => { try { const result = this.compiler.compile(input, `test-fact-cache-${Date.now()}`); this.assert(result.success, `${description} should parse successfully`); console.log(` ✓ ${description}`); } catch (error) { this.fail(`Fact cache test: ${description}`, error); } }); } testFactLimits() { console.log('Testing Fact Limits...'); const testCases = [ { input: `fact isMember(user: User, group: Group) limit 10`, description: 'Fact with simple limit' }, { input: `fact isFriend(user: User, friend: User) limit 100`, description: 'Fact with higher limit' }, { input: `fact isMember(user: User, group: Group) transitive limit 5`, description: 'Transitive fact with limit' }, { input: `fact isFriend(user: User, friend: User) symmetrical limit 50`, description: 'Symmetrical fact with limit' }, { input: `fact isMember(user: User, group: Group) transitive CACHE lazy limit 3`, description: 'Fact with properties, caching, and limit' } ]; testCases.forEach(({ input, description }) => { try { const result = this.compiler.compile(input, `test-fact-limit-${Date.now()}`); this.assert(result.success, `${description} should parse successfully`); console.log(` ✓ ${description}`); } catch (error) { this.fail(`Fact limit test: ${description}`, error); } }); } testParameterTypes() { console.log('Testing Parameter Types...'); const testCases = [ { type: 'string', description: 'String parameter' }, { type: 'number', description: 'Number parameter' }, { type: 'boolean', description: 'Boolean parameter' }, { type: 'timestamp', description: 'Timestamp parameter' }, { type: 'User', description: 'Custom type parameter' }, { type: 'Group', description: 'Another custom type parameter' }, { type: 'Permission[]', description: 'Array type parameter' } ]; testCases.forEach(({ type, description }) => { try { const dsl = `fact test(param: ${type})`; const result = this.compiler.compile(dsl, `test-param-type-${Date.now()}`); this.assert(result.success, `${description} should parse successfully`); console.log(` ✓ ${description}`); } catch (error) { this.fail(`Parameter type test: ${description}`, error); } }); } testComplexFacts() { console.log('Testing Complex Facts...'); const testCases = [ { input: `fact hasRole(user: User, role: string) CACHE eager fact isMember(user: User, group: Group) transitive CACHE lazy limit 10 fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100 fact owns(user: User, doc: Document) CACHE eager fact isSuspended(user: User) CACHE lazy`, description: 'Multiple facts with different configurations' }, { input: `fact hasPermission(user: User, resource: Resource, action: string) CACHE eager fact isAdmin(user: User) CACHE eager fact isOwner(user: User, resource: Resource) CACHE eager fact hasAccess(user: User, resource: Resource, level: string) CACHE lazy`, description: 'Permission-related facts' }, { input: `fact isMember(user: User, group: Group) transitive CACHE lazy limit 5 fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 50 fact isColleague(user: User, colleague: User) symmetrical CACHE lazy limit 20 fact isParentOf(parent: User, child: User) transitive CACHE eager limit 3`, description: 'Relationship facts with various properties' } ]; testCases.forEach(({ input, description }) => { try { const result = this.compiler.compile(input, `test-complex-facts-${Date.now()}`); this.assert(result.success, `${description} should parse successfully`); this.assert(result.program.facts.length > 0, 'Should have facts'); console.log(` ✓ ${description}`); } catch (error) { this.fail(`Complex facts test: ${description}`, error); } }); } testFactErrors() { console.log('Testing Fact Error Handling...'); const testCases = [ { input: `fact hasRole(user: User, role: string`, description: 'Missing closing parenthesis should fail' }, { input: `fact hasRole(user: User, )`, description: 'Missing parameter name should fail' }, { input: `fact hasRole(user: User, role: )`, description: 'Missing parameter type should fail' }, { input: `fact hasRole(, role: string)`, description: 'Missing parameter name should fail' }, { input: `fact hasRole(user: User, role: string) CACHE`, description: 'Incomplete cache directive should fail' }, { input: `fact hasRole(user: User, role: string) limit`, description: 'Incomplete limit should fail' }, { input: `fact hasRole(user: User, role: string) invalid`, description: 'Invalid property should fail' } ]; testCases.forEach(({ input, description }) => { try { const result = this.compiler.compile(input, `test-fact-error-${Date.now()}`); this.assert(!result.success, `${description} should fail to parse`); console.log(` ✓ ${description} (correctly failed)`); } catch (error) { // Expected to fail console.log(` ✓ ${description} (correctly failed)`); } }); } assert(condition, message) { if (!condition) { throw new Error(`Assertion failed: ${message}`); } } fail(testName, error) { console.log(` ✗ ${testName} failed: ${error.message}`); this.testResults.push({ test: testName, status: 'FAILED', error: error.message }); } getTestResults() { const passed = this.testResults.filter(r => r.status === 'PASSED').length; const failed = this.testResults.filter(r => r.status === 'FAILED').length; const total = this.testResults.length; return { total: total, passed: passed, failed: failed, success: failed === 0, results: this.testResults }; } } export function runFactTests(arbiter) { const test = new FactTests(); test.setup(arbiter); return test.runAllTests(); }