cleanup: remove dead code, stale shipped scaffolding, internal docs
Dead code with zero callers (deprecation notes promised removal): - RelationCSR index: always-off option (useRelationCsrIndex), never enabled in production, wired through RelationManager/RelationUpdates/ RelationLookup. Removed the module and all wiring. - getAggregatedBlurredValue (RelationManager) and aggregateBlurredValues (ValueManager): @deprecated shims, zero callers. - QualitativeRelationalComparatorRule._aggregateBlurredValues: @deprecated shim, zero callers. Kept compareRelationValues: non-deprecated public API, coherent and clock-threaded, just currently callerless. Stale scaffolding shipping in the published artifact (files: src/): - src/ast/tests/* and src/ast/examples/*: orphaned duplicates of tests/ast/, zero references anywhere, 11 files in the tarball. Removed; the live copies live in tests/ast/. Internal docs moved out of the shipped surface (1266 lines) to docs/internal/: VALUE_OPTIMIZATION_SUMMARY, rules API_SPECIFICATION, ast README, qualitative README — repo-kept, not packaged. Tarball .md count: 11 -> 1. Rigor 251/251, full suite 853/791/0.
This commit is contained in:
@@ -1,352 +0,0 @@
|
||||
import { DSLCompiler } from '../DSLCompiler.js';
|
||||
import { Arbiter } from '../../core/Arbiter.js';
|
||||
|
||||
/**
|
||||
* Progressive Test Suite - Tests each language feature incrementally
|
||||
*/
|
||||
export function runProgressiveTestSuite() {
|
||||
console.log('=== Progressive DSL Test Suite ===\n');
|
||||
|
||||
const arbiter = new Arbiter();
|
||||
const compiler = new DSLCompiler(arbiter);
|
||||
|
||||
const tests = [
|
||||
{
|
||||
name: '1. Basic Definitions',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
age: number
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 0, evidence: 0, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '2. Definitions with Arrays',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
roles: string[]
|
||||
permissions: Permission[]
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 0, evidence: 0, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '3. Definitions with Behaviors',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
lastActive: timestamp BEHAVES {
|
||||
decaying down hourly
|
||||
}
|
||||
score: number BEHAVES {
|
||||
blurring adaptive confidence_95
|
||||
}
|
||||
session: string BEHAVES {
|
||||
ttl 24h
|
||||
}
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 0, evidence: 0, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '4. Definitions with Cache Directives',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
lastActive: timestamp BEHAVES {
|
||||
decaying down hourly
|
||||
} CACHE lazy
|
||||
score: number BEHAVES {
|
||||
blurring adaptive confidence_95
|
||||
} CACHE eager
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 0, evidence: 0, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '5. Basic Facts',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
fact isActive(user: User)`,
|
||||
expected: { definitions: 1, facts: 2, evidence: 0, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '6. Facts with Properties',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string) CACHE eager
|
||||
fact isMember(user: User, group: Group) transitive CACHE lazy
|
||||
fact isFriend(user: User, friend: User) symmetrical`,
|
||||
expected: { definitions: 1, facts: 3, evidence: 0, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '7. Facts with Limits',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact isMember(user: User, group: Group) transitive limit 10
|
||||
fact isFriend(user: User, friend: User) symmetrical limit 100`,
|
||||
expected: { definitions: 1, facts: 2, evidence: 0, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '8. Simple Evidence',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
hasRole(user, 'admin')
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 1, evidence: 1, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '9. Evidence with Multiple Statements',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
fact owns(user: User, doc: Document)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
owns(user, doc)
|
||||
hasRole(user, 'admin')
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 2, evidence: 1, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '10. Evidence with ALWAYS',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact isActive(user: User)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
ALWAYS isActive(user)
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 1, evidence: 1, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '11. Evidence with REQUIRES',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasClearance(user: User, level: string)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
REQUIRES hasClearance(user, doc.level)
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 1, evidence: 1, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '12. Evidence with WHEN',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
WHEN hasRole(user, 'admin')
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 1, evidence: 1, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '13. Evidence with WHEN UNLESS',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
fact isSuspended(user: User)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 2, evidence: 1, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '14. Evidence with Pattern Matching',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact isMember(user: User, group: Group)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
isMember(user, *group) {
|
||||
canRead(group, doc)
|
||||
}
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 1, evidence: 1, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '15. Evidence with Pattern Matching and Limits',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact isMember(user: User, group: Group)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
isMember(user, *group) {
|
||||
canRead(group, doc)
|
||||
} limit 5
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 1, evidence: 1, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '16. Evidence with Fusion',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
fact isMember(user: User, group: Group)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
fusion max {
|
||||
hasRole(user, 'admin')
|
||||
isMember(user, *group) {
|
||||
canRead(group, doc)
|
||||
}
|
||||
}
|
||||
}`,
|
||||
expected: { definitions: 1, facts: 2, evidence: 1, measures: 0 }
|
||||
},
|
||||
{
|
||||
name: '17. Basic Measures',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
role: string
|
||||
}
|
||||
|
||||
measure userRole(user: User) {
|
||||
user.role
|
||||
} PROVIDES string`,
|
||||
expected: { definitions: 1, facts: 0, evidence: 0, measures: 1 }
|
||||
},
|
||||
{
|
||||
name: '18. Measures with Fusion',
|
||||
dsl: `
|
||||
definition User {
|
||||
name: string
|
||||
role: string
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
|
||||
measure userPermissions(user: User) {
|
||||
fusion max {
|
||||
user.role.permissions
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
} PROVIDES Permission[]`,
|
||||
expected: { definitions: 1, facts: 1, evidence: 0, measures: 1 }
|
||||
},
|
||||
{
|
||||
name: '19. Complete Example',
|
||||
dsl: `
|
||||
definition User {
|
||||
role: string
|
||||
isActive: boolean
|
||||
lastActive: timestamp BEHAVES {
|
||||
decaying down hourly
|
||||
} CACHE lazy
|
||||
}
|
||||
|
||||
definition Document {
|
||||
level: string
|
||||
owner: User
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string) CACHE eager
|
||||
fact owns(user: User, doc: Document) CACHE eager
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
owns(user, doc)
|
||||
|
||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||
}
|
||||
|
||||
measure userRole(user: User) {
|
||||
user.role
|
||||
} PROVIDES string`,
|
||||
expected: { definitions: 2, facts: 2, evidence: 1, measures: 1 }
|
||||
}
|
||||
];
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const test of tests) {
|
||||
console.log(`\n--- ${test.name} ---`);
|
||||
|
||||
try {
|
||||
const result = compiler.compile(test.dsl, `test-${passed + failed + 1}`);
|
||||
|
||||
const actual = {
|
||||
definitions: result.program.definitions.length,
|
||||
facts: result.program.facts.length,
|
||||
evidence: result.program.evidence.length,
|
||||
measures: result.program.measures.length
|
||||
};
|
||||
|
||||
const success = result.success &&
|
||||
actual.definitions === test.expected.definitions &&
|
||||
actual.facts === test.expected.facts &&
|
||||
actual.evidence === test.expected.evidence &&
|
||||
actual.measures === test.expected.measures;
|
||||
|
||||
if (success) {
|
||||
console.log('✅ PASSED');
|
||||
console.log(` Definitions: ${actual.definitions}, Facts: ${actual.facts}, Evidence: ${actual.evidence}, Measures: ${actual.measures}`);
|
||||
passed++;
|
||||
} else {
|
||||
console.log('❌ FAILED');
|
||||
console.log(` Expected: ${JSON.stringify(test.expected)}`);
|
||||
console.log(` Actual: ${JSON.stringify(actual)}`);
|
||||
if (result.errors.length > 0) {
|
||||
console.log(` Errors: ${result.errors.join(', ')}`);
|
||||
}
|
||||
failed++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ FAILED');
|
||||
console.log(` Error: ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n=== Test Suite Results ===`);
|
||||
console.log(`Total Tests: ${passed + failed}`);
|
||||
console.log(`Passed: ${passed}`);
|
||||
console.log(`Failed: ${failed}`);
|
||||
console.log(`Success Rate: ${((passed / (passed + failed)) * 100).toFixed(1)}%`);
|
||||
|
||||
return { passed, failed, total: passed + failed };
|
||||
}
|
||||
|
||||
// Run the test suite
|
||||
runProgressiveTestSuite();
|
||||
@@ -1,53 +0,0 @@
|
||||
import { DSLCompiler } from '../DSLCompiler.js';
|
||||
import { Arbiter } from '../../core/Arbiter.js';
|
||||
|
||||
/**
|
||||
* Test with simple evidence containing predicate calls
|
||||
*/
|
||||
export function runSimpleEvidenceTest() {
|
||||
console.log('=== Simple Evidence Test ===\n');
|
||||
|
||||
// Create arbiter instance
|
||||
const arbiter = new Arbiter();
|
||||
|
||||
// Create compiler
|
||||
const compiler = new DSLCompiler(arbiter);
|
||||
|
||||
// Simple evidence with predicate call
|
||||
const dsl = `
|
||||
definition User {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string)
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
hasRole(user, 'admin')
|
||||
}
|
||||
`;
|
||||
|
||||
console.log('1. Compiling DSL with simple evidence...');
|
||||
try {
|
||||
const result = compiler.compile(dsl, 'simple-evidence');
|
||||
|
||||
console.log('Result:', result.success ? 'SUCCESS' : 'FAILED');
|
||||
console.log('Generated rules:', result.generatedRules.size);
|
||||
console.log('Program evidence:', result.program.evidence.length);
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
console.log('Errors:', result.errors);
|
||||
}
|
||||
|
||||
if (result.warnings.length > 0) {
|
||||
console.log('Warnings:', result.warnings);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Compilation failed:', error.message);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
runSimpleEvidenceTest();
|
||||
@@ -1,381 +0,0 @@
|
||||
/**
|
||||
* Type Definition Tests
|
||||
*
|
||||
* Tests the type definition system of the Evidence DSL,
|
||||
* including fields, behaviors, caching, and complex type structures.
|
||||
*/
|
||||
|
||||
import { DSLCompiler } from '../DSLCompiler.js';
|
||||
|
||||
export class DefinitionTests {
|
||||
constructor() {
|
||||
this.arbiter = null;
|
||||
this.compiler = null;
|
||||
this.testResults = [];
|
||||
}
|
||||
|
||||
setup(arbiter) {
|
||||
this.arbiter = arbiter;
|
||||
this.compiler = new DSLCompiler(arbiter);
|
||||
}
|
||||
|
||||
runAllTests() {
|
||||
console.log('=== Type Definition Tests ===\n');
|
||||
|
||||
this.testBasicDefinitions();
|
||||
this.testFieldTypes();
|
||||
this.testArrayTypes();
|
||||
this.testBehaviors();
|
||||
this.testCaching();
|
||||
this.testComplexDefinitions();
|
||||
this.testDefinitionErrors();
|
||||
|
||||
return this.getTestResults();
|
||||
}
|
||||
|
||||
testBasicDefinitions() {
|
||||
console.log('Testing Basic Definitions...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `definition User { role: string }`,
|
||||
description: 'Simple definition with one field'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
role: string
|
||||
isActive: boolean
|
||||
}`,
|
||||
description: 'Definition with multiple fields'
|
||||
},
|
||||
{
|
||||
input: `definition Group {
|
||||
name: string
|
||||
description: string
|
||||
created: timestamp
|
||||
}`,
|
||||
description: 'Definition with different field types'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-basic-def-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
this.assert(result.program.definitions.length > 0, 'Should have definitions');
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Basic definition test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testFieldTypes() {
|
||||
console.log('Testing Field Types...');
|
||||
|
||||
const testCases = [
|
||||
{ type: 'string', description: 'String field type' },
|
||||
{ type: 'number', description: 'Number field type' },
|
||||
{ type: 'boolean', description: 'Boolean field type' },
|
||||
{ type: 'timestamp', description: 'Timestamp field type' },
|
||||
{ type: 'User', description: 'Custom type field' },
|
||||
{ type: 'Permission', description: 'Another custom type field' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ type, description }) => {
|
||||
try {
|
||||
const dsl = `definition Test { field: ${type} }`;
|
||||
const result = this.compiler.compile(dsl, `test-field-type-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Field type test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testArrayTypes() {
|
||||
console.log('Testing Array Types...');
|
||||
|
||||
const testCases = [
|
||||
{ type: 'string[]', description: 'String array' },
|
||||
{ type: 'number[]', description: 'Number array' },
|
||||
{ type: 'boolean[]', description: 'Boolean array' },
|
||||
{ type: 'Permission[]', description: 'Custom type array' },
|
||||
{ type: 'User[]', description: 'User array' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ type, description }) => {
|
||||
try {
|
||||
const dsl = `definition Test { items: ${type} }`;
|
||||
const result = this.compiler.compile(dsl, `test-array-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Array type test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testBehaviors() {
|
||||
console.log('Testing Behaviors...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `definition User {
|
||||
balance: number BEHAVES { decaying down hourly }
|
||||
}`,
|
||||
description: 'Decay behavior - down hourly'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
reputation: number BEHAVES { decaying up daily }
|
||||
}`,
|
||||
description: 'Decay behavior - up daily'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
score: number BEHAVES { decaying neutral weekly }
|
||||
}`,
|
||||
description: 'Decay behavior - neutral weekly'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
stability: number BEHAVES { decaying stable monthly }
|
||||
}`,
|
||||
description: 'Decay behavior - stable monthly'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
confidence: number BEHAVES { blurring fixed }
|
||||
}`,
|
||||
description: 'Blur behavior - fixed'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
accuracy: number BEHAVES { blurring adaptive }
|
||||
}`,
|
||||
description: 'Blur behavior - adaptive'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
precision: number BEHAVES { blurring confidence confidence_90 }
|
||||
}`,
|
||||
description: 'Blur behavior - confidence with level'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
session: string BEHAVES { ttl 1h }
|
||||
}`,
|
||||
description: 'TTL behavior - hours'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
token: string BEHAVES { ttl 24h }
|
||||
}`,
|
||||
description: 'TTL behavior - 24 hours'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
cache: string BEHAVES { ttl 7d }
|
||||
}`,
|
||||
description: 'TTL behavior - days'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-behavior-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Behavior test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testCaching() {
|
||||
console.log('Testing Caching...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `definition User {
|
||||
role: string CACHE eager
|
||||
}`,
|
||||
description: 'Eager caching'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
score: number CACHE lazy
|
||||
}`,
|
||||
description: 'Lazy caching'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
balance: number BEHAVES { decaying down hourly } CACHE eager
|
||||
}`,
|
||||
description: 'Behavior with eager caching'
|
||||
},
|
||||
{
|
||||
input: `definition User {
|
||||
reputation: number BEHAVES { blurring adaptive } CACHE lazy
|
||||
}`,
|
||||
description: 'Behavior with lazy caching'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-cache-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Cache test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testComplexDefinitions() {
|
||||
console.log('Testing Complex Definitions...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `definition User {
|
||||
role: string
|
||||
isActive: boolean
|
||||
lastActive: timestamp BEHAVES {
|
||||
decaying down hourly
|
||||
} CACHE lazy
|
||||
isSuspended: boolean
|
||||
balance: number BEHAVES {
|
||||
decaying down hourly
|
||||
} CACHE eager
|
||||
score: number BEHAVES {
|
||||
blurring adaptive confidence_95
|
||||
} CACHE lazy
|
||||
session: string BEHAVES {
|
||||
ttl 24h
|
||||
} CACHE eager
|
||||
}`,
|
||||
description: 'Complex definition with multiple behaviors and caching'
|
||||
},
|
||||
{
|
||||
input: `definition Group {
|
||||
name: string
|
||||
permissions: Permission[]
|
||||
members: User[]
|
||||
created: timestamp BEHAVES {
|
||||
decaying stable monthly
|
||||
} CACHE lazy
|
||||
isPublic: boolean CACHE eager
|
||||
}`,
|
||||
description: 'Definition with arrays and mixed behaviors'
|
||||
},
|
||||
{
|
||||
input: `definition Document {
|
||||
level: string
|
||||
owner: User
|
||||
tags: string[]
|
||||
content: string BEHAVES {
|
||||
blurring fixed
|
||||
} CACHE lazy
|
||||
accessCount: number BEHAVES {
|
||||
decaying up daily
|
||||
} CACHE eager
|
||||
expiresAt: timestamp BEHAVES {
|
||||
ttl 30d
|
||||
} CACHE eager
|
||||
}`,
|
||||
description: 'Definition with all behavior types'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-complex-def-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
this.assert(result.program.definitions.length > 0, 'Should have definitions');
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Complex definition test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testDefinitionErrors() {
|
||||
console.log('Testing Definition Error Handling...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `definition User { role: string`,
|
||||
description: 'Missing closing brace should fail'
|
||||
},
|
||||
{
|
||||
input: `definition User { role: }`,
|
||||
description: 'Missing field type should fail'
|
||||
},
|
||||
{
|
||||
input: `definition User { : string }`,
|
||||
description: 'Missing field name should fail'
|
||||
},
|
||||
{
|
||||
input: `definition User { role: string BEHAVES { }`,
|
||||
description: 'Incomplete behavior should fail'
|
||||
},
|
||||
{
|
||||
input: `definition User { role: string CACHE }`,
|
||||
description: 'Incomplete cache directive should fail'
|
||||
},
|
||||
{
|
||||
input: `definition User { role: string BEHAVES { invalid } }`,
|
||||
description: 'Invalid behavior should fail'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-def-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 runDefinitionTests(arbiter) {
|
||||
const test = new DefinitionTests();
|
||||
test.setup(arbiter);
|
||||
return test.runAllTests();
|
||||
}
|
||||
@@ -1,464 +0,0 @@
|
||||
/**
|
||||
* Evidence Rule Tests
|
||||
*
|
||||
* Tests the evidence rule system of the Evidence DSL,
|
||||
* including defeasible logic, pattern matching, fusion, and complex evidence composition.
|
||||
*/
|
||||
|
||||
import { DSLCompiler } from '../DSLCompiler.js';
|
||||
|
||||
export class EvidenceTests {
|
||||
constructor() {
|
||||
this.arbiter = null;
|
||||
this.compiler = null;
|
||||
this.testResults = [];
|
||||
}
|
||||
|
||||
setup(arbiter) {
|
||||
this.arbiter = arbiter;
|
||||
this.compiler = new DSLCompiler(arbiter);
|
||||
}
|
||||
|
||||
runAllTests() {
|
||||
console.log('=== Evidence Rule Tests ===\n');
|
||||
|
||||
this.testBasicEvidence();
|
||||
this.testDefeasibleLogic();
|
||||
this.testPatternMatching();
|
||||
this.testFusion();
|
||||
this.testComplexEvidence();
|
||||
this.testEvidenceErrors();
|
||||
|
||||
return this.getTestResults();
|
||||
}
|
||||
|
||||
testBasicEvidence() {
|
||||
console.log('Testing 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 }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-basic-evidence-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
this.assert(result.program.evidence.length > 0, 'Should have evidence');
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Basic evidence test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testDefeasibleLogic() {
|
||||
console.log('Testing 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 }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-defeasible-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Defeasible logic test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testPatternMatching() {
|
||||
console.log('Testing 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 }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-pattern-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Pattern matching test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testFusion() {
|
||||
console.log('Testing 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 }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-fusion-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Fusion test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testComplexEvidence() {
|
||||
console.log('Testing 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 }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-complex-evidence-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Complex evidence test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testEvidenceErrors() {
|
||||
console.log('Testing 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 = this.compiler.compile(input, `test-evidence-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 runEvidenceTests(arbiter) {
|
||||
const test = new EvidenceTests();
|
||||
test.setup(arbiter);
|
||||
return test.runAllTests();
|
||||
}
|
||||
@@ -1,347 +0,0 @@
|
||||
/**
|
||||
* Expression Parsing Tests
|
||||
*
|
||||
* Tests the expression parsing capabilities of the Evidence DSL,
|
||||
* focusing on operator precedence, associativity, and complex expressions.
|
||||
*/
|
||||
|
||||
import { DSLCompiler } from '../DSLCompiler.js';
|
||||
|
||||
export class ExpressionTests {
|
||||
constructor() {
|
||||
this.arbiter = null;
|
||||
this.compiler = null;
|
||||
this.testResults = [];
|
||||
}
|
||||
|
||||
setup(arbiter) {
|
||||
this.arbiter = arbiter;
|
||||
this.compiler = new DSLCompiler(arbiter);
|
||||
}
|
||||
|
||||
runAllTests() {
|
||||
console.log('=== Expression Parsing Tests ===\n');
|
||||
|
||||
this.testArithmeticPrecedence();
|
||||
this.testLogicalPrecedence();
|
||||
this.testComparisonOperators();
|
||||
this.testTemporalExpressions();
|
||||
this.testUnaryOperators();
|
||||
this.testAttributeAccess();
|
||||
this.testFunctionCalls();
|
||||
this.testComplexExpressions();
|
||||
this.testExpressionErrors();
|
||||
|
||||
return this.getTestResults();
|
||||
}
|
||||
|
||||
testArithmeticPrecedence() {
|
||||
console.log('Testing Arithmetic Operator Precedence...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: '1 + 2 * 3',
|
||||
expected: 'Should evaluate as 1 + (2 * 3) = 7',
|
||||
description: 'Multiplication before addition'
|
||||
},
|
||||
{
|
||||
input: '10 - 3 * 2',
|
||||
expected: 'Should evaluate as 10 - (3 * 2) = 4',
|
||||
description: 'Multiplication before subtraction'
|
||||
},
|
||||
{
|
||||
input: '8 / 2 * 4',
|
||||
expected: 'Should evaluate as (8 / 2) * 4 = 16',
|
||||
description: 'Left-associative division and multiplication'
|
||||
},
|
||||
{
|
||||
input: '2 + 3 * 4 - 5',
|
||||
expected: 'Should evaluate as 2 + (3 * 4) - 5 = 9',
|
||||
description: 'Mixed arithmetic with correct precedence'
|
||||
},
|
||||
{
|
||||
input: '(1 + 2) * 3',
|
||||
expected: 'Should evaluate as (1 + 2) * 3 = 9',
|
||||
description: 'Parentheses override precedence'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, expected, description }) => {
|
||||
try {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const result = this.compiler.compile(dsl, `test-arithmetic-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Arithmetic precedence test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testLogicalPrecedence() {
|
||||
console.log('Testing Logical Operator Precedence...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: 'true && false || true',
|
||||
expected: 'Should evaluate as (true && false) || true = true',
|
||||
description: 'AND before OR'
|
||||
},
|
||||
{
|
||||
input: 'false || true && false',
|
||||
expected: 'Should evaluate as false || (true && false) = false',
|
||||
description: 'AND before OR (alternative)'
|
||||
},
|
||||
{
|
||||
input: 'NOT true && false',
|
||||
expected: 'Should evaluate as (NOT true) && false = false',
|
||||
description: 'NOT before AND'
|
||||
},
|
||||
{
|
||||
input: 'true && NOT false',
|
||||
expected: 'Should evaluate as true && (NOT false) = true',
|
||||
description: 'NOT before AND (alternative)'
|
||||
},
|
||||
{
|
||||
input: '(true || false) && true',
|
||||
expected: 'Should evaluate as (true || false) && true = true',
|
||||
description: 'Parentheses override logical precedence'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, expected, description }) => {
|
||||
try {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const result = this.compiler.compile(dsl, `test-logical-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Logical precedence test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testComparisonOperators() {
|
||||
console.log('Testing Comparison Operators...');
|
||||
|
||||
const testCases = [
|
||||
{ input: '1 == 1', description: 'Equality comparison' },
|
||||
{ input: '1 != 2', description: 'Inequality comparison' },
|
||||
{ input: '5 > 3', description: 'Greater than' },
|
||||
{ input: '3 < 5', description: 'Less than' },
|
||||
{ input: '4 >= 4', description: 'Greater than or equal' },
|
||||
{ input: '4 <= 4', description: 'Less than or equal' },
|
||||
{ input: '1 == 1 && 2 > 1', description: 'Comparison with logical operators' },
|
||||
{ input: '1 + 2 == 3', description: 'Arithmetic in comparison' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const result = this.compiler.compile(dsl, `test-comparison-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Comparison test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testTemporalExpressions() {
|
||||
console.log('Testing Temporal Expressions...');
|
||||
|
||||
const testCases = [
|
||||
{ input: 'user.lastActive within 1h', description: 'Temporal within expression' },
|
||||
{ input: 'user.lastLogin within 24h', description: 'Temporal within with hours' },
|
||||
{ input: 'user.createdAt within 7d', description: 'Temporal within with days' },
|
||||
{ input: 'user.lastActivity within 1h && user.isActive', description: 'Temporal with logical operators' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const result = this.compiler.compile(dsl, `test-temporal-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Temporal test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testUnaryOperators() {
|
||||
console.log('Testing Unary Operators...');
|
||||
|
||||
const testCases = [
|
||||
{ input: 'NOT true', description: 'NOT operator' },
|
||||
{ input: '!false', description: 'Alternative NOT operator' },
|
||||
{ input: 'NOT (true && false)', description: 'NOT with parenthesized expression' },
|
||||
{ input: 'NOT user.isSuspended', description: 'NOT with attribute access' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const result = this.compiler.compile(dsl, `test-unary-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Unary test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testAttributeAccess() {
|
||||
console.log('Testing Attribute Access...');
|
||||
|
||||
const testCases = [
|
||||
{ input: 'user.role', description: 'Simple attribute access' },
|
||||
{ input: 'user.profile.name', description: 'Nested attribute access' },
|
||||
{ input: 'user.permissions[0]', description: 'Array access' },
|
||||
{ input: 'user.role.permissions[0]', description: 'Nested attribute with array access' },
|
||||
{ input: 'user.isActive && user.role == "admin"', description: 'Attribute access in logical expression' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const result = this.compiler.compile(dsl, `test-attribute-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Attribute access test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testFunctionCalls() {
|
||||
console.log('Testing Function Calls...');
|
||||
|
||||
const testCases = [
|
||||
{ input: 'hasRole(user, "admin")', description: 'Simple function call' },
|
||||
{ input: 'isMember(user, group)', description: 'Function call with variables' },
|
||||
{ input: 'hasPermission(user, resource, "read")', description: 'Function call with multiple arguments' },
|
||||
{ input: 'hasRole(user, "admin") && isActive(user)', description: 'Multiple function calls' },
|
||||
{ input: 'hasRole(user, user.role)', description: 'Function call with attribute access' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const result = this.compiler.compile(dsl, `test-function-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Function call test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testComplexExpressions() {
|
||||
console.log('Testing Complex Expressions...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: 'user.isActive && (hasRole(user, "admin") || hasPermission(user, resource, "read"))',
|
||||
description: 'Complex logical expression with function calls'
|
||||
},
|
||||
{
|
||||
input: 'user.balance > 100 && user.isActive && NOT user.isSuspended',
|
||||
description: 'Multiple conditions with NOT'
|
||||
},
|
||||
{
|
||||
input: 'user.lastActive within 1h && (user.role == "admin" || user.hasEmergencyAccess)',
|
||||
description: 'Temporal with logical conditions'
|
||||
},
|
||||
{
|
||||
input: 'hasRole(user, "admin") && user.isActive && NOT (user.isSuspended || user.isBlacklisted)',
|
||||
description: 'Complex negation with multiple conditions'
|
||||
},
|
||||
{
|
||||
input: 'user.score > 0.8 && user.isTrusted && user.lastActivity within 24h',
|
||||
description: 'Multiple attribute conditions with temporal'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const result = this.compiler.compile(dsl, `test-complex-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Complex expression test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testExpressionErrors() {
|
||||
console.log('Testing Expression Error Handling...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: 'user.role ==',
|
||||
description: 'Incomplete comparison should fail'
|
||||
},
|
||||
{
|
||||
input: 'user.role &&',
|
||||
description: 'Incomplete logical expression should fail'
|
||||
},
|
||||
{
|
||||
input: 'hasRole(user,)',
|
||||
description: 'Function call with missing argument should fail'
|
||||
},
|
||||
{
|
||||
input: 'user.role == "admin" &&',
|
||||
description: 'Incomplete logical expression should fail'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const result = this.compiler.compile(dsl, `test-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 runExpressionTests(arbiter) {
|
||||
const test = new ExpressionTests();
|
||||
test.setup(arbiter);
|
||||
return test.runAllTests();
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -1,635 +0,0 @@
|
||||
/**
|
||||
* Integration Tests
|
||||
*
|
||||
* Tests complex combinations of multiple language features,
|
||||
* simulating real-world authorization scenarios.
|
||||
*/
|
||||
|
||||
import { DSLCompiler } from '../DSLCompiler.js';
|
||||
|
||||
export class IntegrationTests {
|
||||
constructor() {
|
||||
this.arbiter = null;
|
||||
this.compiler = null;
|
||||
this.testResults = [];
|
||||
}
|
||||
|
||||
setup(arbiter) {
|
||||
this.arbiter = arbiter;
|
||||
this.compiler = new DSLCompiler(arbiter);
|
||||
}
|
||||
|
||||
runAllTests() {
|
||||
console.log('=== Integration Tests ===\n');
|
||||
|
||||
this.testCompleteAuthorizationSystem();
|
||||
this.testMultiDomainSystem();
|
||||
this.testHierarchicalAccess();
|
||||
this.testSimilarityBasedAccess();
|
||||
this.testTemporalAccess();
|
||||
this.testComplexBehaviors();
|
||||
this.testPerformanceScenarios();
|
||||
|
||||
return this.getTestResults();
|
||||
}
|
||||
|
||||
testCompleteAuthorizationSystem() {
|
||||
console.log('Testing Complete Authorization System...');
|
||||
|
||||
const completeSystem = `
|
||||
// Type definitions with complex behaviors
|
||||
definition User {
|
||||
role: string
|
||||
isActive: boolean
|
||||
lastActive: timestamp BEHAVES {
|
||||
decaying down hourly
|
||||
} CACHE lazy
|
||||
isSuspended: boolean
|
||||
balance: number BEHAVES {
|
||||
decaying down hourly
|
||||
} CACHE eager
|
||||
score: number BEHAVES {
|
||||
blurring adaptive confidence_95
|
||||
} CACHE lazy
|
||||
session: string BEHAVES {
|
||||
ttl 24h
|
||||
} CACHE eager
|
||||
clearance: string BEHAVES {
|
||||
blurring fixed
|
||||
} CACHE eager
|
||||
reputation: number BEHAVES {
|
||||
decaying up daily
|
||||
} CACHE lazy
|
||||
}
|
||||
|
||||
definition Group {
|
||||
name: string
|
||||
permissions: Permission[]
|
||||
level: string
|
||||
isPublic: boolean CACHE eager
|
||||
created: timestamp BEHAVES {
|
||||
decaying stable monthly
|
||||
} CACHE lazy
|
||||
}
|
||||
|
||||
definition Document {
|
||||
level: string
|
||||
owner: User
|
||||
tags: string[]
|
||||
content: string BEHAVES {
|
||||
blurring fixed
|
||||
} CACHE lazy
|
||||
accessCount: number BEHAVES {
|
||||
decaying up daily
|
||||
} CACHE eager
|
||||
expiresAt: timestamp BEHAVES {
|
||||
ttl 30d
|
||||
} CACHE eager
|
||||
isPublic: boolean CACHE eager
|
||||
}
|
||||
|
||||
definition Resource {
|
||||
level: string
|
||||
owner: User
|
||||
permissions: Permission[]
|
||||
isPublic: boolean CACHE eager
|
||||
accessCount: number BEHAVES {
|
||||
decaying up daily
|
||||
} CACHE eager
|
||||
}
|
||||
|
||||
// Facts with various properties and caching
|
||||
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
|
||||
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
|
||||
fact isColleague(user: User, colleague: User) symmetrical CACHE lazy limit 50
|
||||
fact isParentOf(parent: User, child: User) transitive CACHE eager limit 3
|
||||
|
||||
// Evidence rules with complex logic
|
||||
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)
|
||||
}
|
||||
|
||||
evidence canWrite(user: User, doc: Document) {
|
||||
owns(user, doc)
|
||||
|
||||
isMember(user, *group) {
|
||||
canWrite(group, doc)
|
||||
} limit 3
|
||||
|
||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||
|
||||
REQUIRES user.isActive
|
||||
}
|
||||
|
||||
evidence canDelete(user: User, doc: Document) {
|
||||
owns(user, doc)
|
||||
|
||||
ALWAYS user.isActive
|
||||
|
||||
WHEN hasRole(user, 'admin') UNLESS isSuspended(user)
|
||||
|
||||
REQUIRES user.isActive
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Measures for computed values
|
||||
measure userRole(user: User) {
|
||||
user.role
|
||||
} PROVIDES string
|
||||
|
||||
measure userPermissions(user: User) {
|
||||
fusion max {
|
||||
user.role.permissions
|
||||
user.group.permissions
|
||||
}
|
||||
} PROVIDES Permission[]
|
||||
|
||||
measure effectiveClearance(user: User) {
|
||||
fusion majority {
|
||||
user.clearance
|
||||
user.role.clearance
|
||||
user.group.clearance
|
||||
}
|
||||
} PROVIDES string
|
||||
|
||||
measure userTrustScore(user: User) {
|
||||
fusion average {
|
||||
user.reputation
|
||||
user.activityScore
|
||||
user.verificationLevel
|
||||
}
|
||||
} PROVIDES number
|
||||
|
||||
measure userBalance(user: User) {
|
||||
user.balance
|
||||
} PROVIDES number
|
||||
|
||||
measure userScore(user: User) {
|
||||
user.score
|
||||
} PROVIDES number
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = this.compiler.compile(completeSystem, 'test-complete-system');
|
||||
this.assert(result.success, 'Complete authorization system should compile successfully');
|
||||
this.assert(result.program.definitions.length >= 4, 'Should have multiple definitions');
|
||||
this.assert(result.program.facts.length >= 10, 'Should have multiple facts');
|
||||
this.assert(result.program.evidence.length >= 5, 'Should have multiple evidence rules');
|
||||
this.assert(result.program.measures.length >= 6, 'Should have multiple measures');
|
||||
console.log(' ✓ Complete authorization system');
|
||||
} catch (error) {
|
||||
this.fail('Complete authorization system test', error);
|
||||
}
|
||||
}
|
||||
|
||||
testMultiDomainSystem() {
|
||||
console.log('Testing Multi-Domain System...');
|
||||
|
||||
const multiDomain = `
|
||||
// Authentication domain
|
||||
definition User {
|
||||
role: string
|
||||
isActive: boolean
|
||||
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
||||
session: string BEHAVES { ttl 24h } CACHE eager
|
||||
}
|
||||
|
||||
fact hasRole(user: User, role: string) CACHE eager
|
||||
fact isActive(user: User) CACHE eager
|
||||
|
||||
evidence canAuthenticate(user: User) {
|
||||
user.isActive
|
||||
user.session within 24h
|
||||
}
|
||||
|
||||
// Authorization domain
|
||||
definition Resource {
|
||||
level: string
|
||||
owner: User
|
||||
permissions: Permission[]
|
||||
}
|
||||
|
||||
fact owns(user: User, resource: Resource) CACHE eager
|
||||
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
|
||||
|
||||
evidence canAccess(user: User, resource: Resource) {
|
||||
owns(user, resource)
|
||||
hasPermission(user, resource, 'read')
|
||||
}
|
||||
|
||||
// Finance domain
|
||||
definition Account {
|
||||
balance: number BEHAVES { decaying down hourly } CACHE eager
|
||||
owner: User
|
||||
isActive: boolean CACHE eager
|
||||
}
|
||||
|
||||
fact hasAccount(user: User, account: Account) CACHE eager
|
||||
fact hasBalance(user: User, amount: number) CACHE eager
|
||||
|
||||
evidence canWithdraw(user: User, amount: number) {
|
||||
hasBalance(user, amount)
|
||||
user.isActive
|
||||
}
|
||||
|
||||
// Social domain
|
||||
definition Group {
|
||||
name: string
|
||||
members: User[]
|
||||
isPublic: boolean CACHE eager
|
||||
}
|
||||
|
||||
fact isMember(user: User, group: Group) transitive CACHE lazy limit 10
|
||||
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
|
||||
|
||||
evidence canAccessGroup(user: User, group: Group) {
|
||||
isMember(user, group)
|
||||
group.isPublic
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = this.compiler.compile(multiDomain, 'test-multi-domain');
|
||||
this.assert(result.success, 'Multi-domain system should compile successfully');
|
||||
this.assert(result.program.definitions.length >= 4, 'Should have multiple domain definitions');
|
||||
this.assert(result.program.facts.length >= 8, 'Should have multiple domain facts');
|
||||
this.assert(result.program.evidence.length >= 4, 'Should have multiple domain evidence rules');
|
||||
console.log(' ✓ Multi-domain system');
|
||||
} catch (error) {
|
||||
this.fail('Multi-domain system test', error);
|
||||
}
|
||||
}
|
||||
|
||||
testHierarchicalAccess() {
|
||||
console.log('Testing Hierarchical Access...');
|
||||
|
||||
const hierarchicalSystem = `
|
||||
definition User {
|
||||
role: string
|
||||
level: string
|
||||
isActive: boolean
|
||||
clearance: string
|
||||
}
|
||||
|
||||
definition Organization {
|
||||
name: string
|
||||
level: string
|
||||
parent: Organization
|
||||
}
|
||||
|
||||
fact isMember(user: User, org: Organization) transitive CACHE lazy limit 5
|
||||
fact isParentOf(parent: Organization, child: Organization) transitive CACHE eager limit 3
|
||||
fact hasRole(user: User, role: string) CACHE eager
|
||||
fact hasClearance(user: User, level: string) CACHE eager
|
||||
|
||||
evidence canAccessOrg(user: User, org: Organization) {
|
||||
isMember(user, org)
|
||||
|
||||
isParentOf(org, *parentOrg) {
|
||||
canAccessOrg(user, parentOrg)
|
||||
} limit 3
|
||||
|
||||
WHEN hasRole(user, 'admin') UNLESS user.isSuspended
|
||||
}
|
||||
|
||||
evidence canAccessResource(user: User, resource: Resource) {
|
||||
isMember(user, *org) {
|
||||
canAccessResource(org, resource)
|
||||
} limit 5
|
||||
|
||||
parentOf(user, *parent) {
|
||||
canAccessResource(parent, resource)
|
||||
} limit 2
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = this.compiler.compile(hierarchicalSystem, 'test-hierarchical');
|
||||
this.assert(result.success, 'Hierarchical access system should compile successfully');
|
||||
console.log(' ✓ Hierarchical access system');
|
||||
} catch (error) {
|
||||
this.fail('Hierarchical access test', error);
|
||||
}
|
||||
}
|
||||
|
||||
testSimilarityBasedAccess() {
|
||||
console.log('Testing Similarity-Based Access...');
|
||||
|
||||
const similaritySystem = `
|
||||
definition User {
|
||||
profile: string
|
||||
interests: string[]
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
definition Document {
|
||||
content: string
|
||||
tags: string[]
|
||||
isPublic: boolean
|
||||
owner: User
|
||||
}
|
||||
|
||||
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 100
|
||||
fact hasInterest(user: User, interest: string) CACHE lazy
|
||||
fact hasTag(doc: Document, tag: string) CACHE lazy
|
||||
|
||||
evidence canRead(user: User, doc: Document) {
|
||||
owns(user, doc)
|
||||
|
||||
similar(doc, *similar) |similarity| {
|
||||
canRead(user, similar)
|
||||
similar.isPublic
|
||||
} with similarity > 0.7 limit 10
|
||||
|
||||
isFriend(user, *friend) {
|
||||
canRead(friend, doc)
|
||||
} limit 5
|
||||
|
||||
fusion majority {
|
||||
user.interests
|
||||
doc.tags
|
||||
}
|
||||
}
|
||||
|
||||
evidence canRecommend(user: User, doc: Document) {
|
||||
similar(user, *similarUser) |similarity| {
|
||||
canRead(similarUser, doc)
|
||||
} with similarity > 0.8 limit 20
|
||||
|
||||
fusion average {
|
||||
user.profile
|
||||
doc.content
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = this.compiler.compile(similaritySystem, 'test-similarity');
|
||||
this.assert(result.success, 'Similarity-based access system should compile successfully');
|
||||
console.log(' ✓ Similarity-based access system');
|
||||
} catch (error) {
|
||||
this.fail('Similarity-based access test', error);
|
||||
}
|
||||
}
|
||||
|
||||
testTemporalAccess() {
|
||||
console.log('Testing Temporal Access...');
|
||||
|
||||
const temporalSystem = `
|
||||
definition User {
|
||||
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
||||
session: string BEHAVES { ttl 24h } CACHE eager
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
definition Event {
|
||||
startTime: timestamp
|
||||
endTime: timestamp
|
||||
isPublic: boolean
|
||||
}
|
||||
|
||||
fact hasAccess(user: User, event: Event) CACHE lazy
|
||||
fact isParticipant(user: User, event: Event) CACHE eager
|
||||
|
||||
evidence canAccessEvent(user: User, event: Event) {
|
||||
user.lastActive within 1h
|
||||
|
||||
isParticipant(user, event)
|
||||
|
||||
WHEN event.isPublic UNLESS user.isSuspended
|
||||
|
||||
fusion min {
|
||||
user.session within 24h
|
||||
user.isActive
|
||||
}
|
||||
}
|
||||
|
||||
evidence canAccessHistorical(user: User, event: Event) {
|
||||
user.lastActive within 24h
|
||||
|
||||
fusion majority {
|
||||
user.isActive
|
||||
user.session within 24h
|
||||
event.isPublic
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = this.compiler.compile(temporalSystem, 'test-temporal');
|
||||
this.assert(result.success, 'Temporal access system should compile successfully');
|
||||
console.log(' ✓ Temporal access system');
|
||||
} catch (error) {
|
||||
this.fail('Temporal access test', error);
|
||||
}
|
||||
}
|
||||
|
||||
testComplexBehaviors() {
|
||||
console.log('Testing Complex Behaviors...');
|
||||
|
||||
const behaviorSystem = `
|
||||
definition User {
|
||||
balance: number BEHAVES { decaying down hourly } CACHE eager
|
||||
score: number BEHAVES { blurring adaptive confidence_95 } CACHE lazy
|
||||
session: string BEHAVES { ttl 24h } CACHE eager
|
||||
reputation: number BEHAVES { decaying up daily } CACHE lazy
|
||||
clearance: string BEHAVES { blurring fixed } CACHE eager
|
||||
lastActive: timestamp BEHAVES { decaying down hourly } CACHE lazy
|
||||
}
|
||||
|
||||
definition Document {
|
||||
content: string BEHAVES { blurring fixed } CACHE lazy
|
||||
accessCount: number BEHAVES { decaying up daily } CACHE eager
|
||||
expiresAt: timestamp BEHAVES { ttl 30d } CACHE eager
|
||||
isPublic: boolean CACHE eager
|
||||
}
|
||||
|
||||
fact hasBalance(user: User, amount: number) CACHE eager
|
||||
fact hasScore(user: User, score: number) CACHE lazy
|
||||
fact hasReputation(user: User, reputation: number) CACHE lazy
|
||||
|
||||
evidence canAccessDocument(user: User, doc: Document) {
|
||||
user.balance > 0
|
||||
|
||||
user.score > 0.5
|
||||
|
||||
user.reputation > 0.3
|
||||
|
||||
doc.accessCount < 1000
|
||||
|
||||
fusion majority {
|
||||
user.isActive
|
||||
user.lastActive within 1h
|
||||
doc.isPublic
|
||||
}
|
||||
}
|
||||
|
||||
measure userEffectiveScore(user: User) {
|
||||
fusion average {
|
||||
user.score
|
||||
user.reputation
|
||||
user.balance
|
||||
}
|
||||
} PROVIDES number
|
||||
|
||||
measure documentPopularity(doc: Document) {
|
||||
doc.accessCount
|
||||
} PROVIDES number
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = this.compiler.compile(behaviorSystem, 'test-behaviors');
|
||||
this.assert(result.success, 'Complex behaviors system should compile successfully');
|
||||
console.log(' ✓ Complex behaviors system');
|
||||
} catch (error) {
|
||||
this.fail('Complex behaviors test', error);
|
||||
}
|
||||
}
|
||||
|
||||
testPerformanceScenarios() {
|
||||
console.log('Testing Performance Scenarios...');
|
||||
|
||||
const performanceSystem = `
|
||||
definition User {
|
||||
role: string
|
||||
isActive: boolean
|
||||
permissions: Permission[] CACHE eager
|
||||
}
|
||||
|
||||
definition Resource {
|
||||
level: string
|
||||
owner: User
|
||||
permissions: Permission[] CACHE eager
|
||||
}
|
||||
|
||||
// High-frequency facts with limits
|
||||
fact isMember(user: User, group: Group) transitive CACHE lazy limit 5
|
||||
fact isFriend(user: User, friend: User) symmetrical CACHE eager limit 50
|
||||
fact hasPermission(user: User, resource: Resource, action: string) CACHE eager
|
||||
fact owns(user: User, resource: Resource) CACHE eager
|
||||
|
||||
// Optimized evidence rules
|
||||
evidence canAccess(user: User, resource: Resource) {
|
||||
owns(user, resource)
|
||||
|
||||
isMember(user, *group) {
|
||||
canAccess(group, resource)
|
||||
} limit 3
|
||||
|
||||
WHEN hasPermission(user, resource, 'read')
|
||||
}
|
||||
|
||||
evidence canModify(user: User, resource: Resource) {
|
||||
owns(user, resource)
|
||||
|
||||
isMember(user, *group) {
|
||||
canModify(group, resource)
|
||||
} limit 2
|
||||
|
||||
WHEN hasPermission(user, resource, 'write')
|
||||
}
|
||||
|
||||
// Efficient measures
|
||||
measure userEffectivePermissions(user: User) {
|
||||
user.permissions
|
||||
} PROVIDES Permission[]
|
||||
|
||||
measure resourceAccessLevel(resource: Resource) {
|
||||
resource.level
|
||||
} PROVIDES string
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = this.compiler.compile(performanceSystem, 'test-performance');
|
||||
this.assert(result.success, 'Performance scenarios should compile successfully');
|
||||
console.log(' ✓ Performance scenarios');
|
||||
} catch (error) {
|
||||
this.fail('Performance scenarios test', error);
|
||||
}
|
||||
}
|
||||
|
||||
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 runIntegrationTests(arbiter) {
|
||||
const test = new IntegrationTests();
|
||||
test.setup(arbiter);
|
||||
return test.runAllTests();
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
/**
|
||||
* Measure Definition Tests
|
||||
*
|
||||
* Tests the measure system of the Evidence DSL,
|
||||
* including aggregation, fusion, return types, and value computation.
|
||||
*/
|
||||
|
||||
import { DSLCompiler } from '../DSLCompiler.js';
|
||||
|
||||
export class MeasureTests {
|
||||
constructor() {
|
||||
this.arbiter = null;
|
||||
this.compiler = null;
|
||||
this.testResults = [];
|
||||
}
|
||||
|
||||
setup(arbiter) {
|
||||
this.arbiter = arbiter;
|
||||
this.compiler = new DSLCompiler(arbiter);
|
||||
}
|
||||
|
||||
runAllTests() {
|
||||
console.log('=== Measure Definition Tests ===\n');
|
||||
|
||||
this.testBasicMeasures();
|
||||
this.testMeasureReturnTypes();
|
||||
this.testMeasureAggregation();
|
||||
this.testMeasureFusion();
|
||||
this.testComplexMeasures();
|
||||
this.testMeasureErrors();
|
||||
|
||||
return this.getTestResults();
|
||||
}
|
||||
|
||||
testBasicMeasures() {
|
||||
console.log('Testing Basic Measures...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `measure userRole(user: User) {
|
||||
user.role
|
||||
} PROVIDES string`,
|
||||
description: 'Simple measure with attribute access'
|
||||
},
|
||||
{
|
||||
input: `measure userBalance(user: User) {
|
||||
user.balance
|
||||
} PROVIDES number`,
|
||||
description: 'Measure accessing numeric attribute'
|
||||
},
|
||||
{
|
||||
input: `measure isUserActive(user: User) {
|
||||
user.isActive
|
||||
} PROVIDES boolean`,
|
||||
description: 'Measure accessing boolean attribute'
|
||||
},
|
||||
{
|
||||
input: `measure userPermissions(user: User) {
|
||||
user.permissions
|
||||
} PROVIDES Permission[]`,
|
||||
description: 'Measure accessing array attribute'
|
||||
},
|
||||
{
|
||||
input: `measure userScore(user: User) {
|
||||
user.score
|
||||
} PROVIDES number`,
|
||||
description: 'Measure with behavior-inherited attribute'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-basic-measure-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
this.assert(result.program.measures.length > 0, 'Should have measures');
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Basic measure test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testMeasureReturnTypes() {
|
||||
console.log('Testing Measure Return Types...');
|
||||
|
||||
const testCases = [
|
||||
{ type: 'string', description: 'String return type' },
|
||||
{ type: 'number', description: 'Number return type' },
|
||||
{ type: 'boolean', description: 'Boolean return type' },
|
||||
{ type: 'timestamp', description: 'Timestamp return type' },
|
||||
{ type: 'Permission[]', description: 'Array return type' },
|
||||
{ type: 'User', description: 'Custom type return' },
|
||||
{ type: 'Group[]', description: 'Custom array return type' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ type, description }) => {
|
||||
try {
|
||||
const dsl = `measure test() { true } PROVIDES ${type}`;
|
||||
const result = this.compiler.compile(dsl, `test-measure-return-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Measure return type test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testMeasureAggregation() {
|
||||
console.log('Testing Measure Aggregation...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `measure userPermissions(user: User) {
|
||||
aggregate {
|
||||
user.role.permissions
|
||||
user.group.permissions
|
||||
} USING majority
|
||||
} PROVIDES Permission[]`,
|
||||
description: 'Aggregation with majority strategy'
|
||||
},
|
||||
{
|
||||
input: `measure userClearance(user: User) {
|
||||
aggregate {
|
||||
user.clearance
|
||||
user.role.clearance
|
||||
user.group.clearance
|
||||
} USING max
|
||||
} PROVIDES string`,
|
||||
description: 'Aggregation with max strategy'
|
||||
},
|
||||
{
|
||||
input: `measure userScore(user: User) {
|
||||
aggregate {
|
||||
user.reputation
|
||||
user.activityScore
|
||||
user.verificationLevel
|
||||
} USING average
|
||||
} PROVIDES number`,
|
||||
description: 'Aggregation with average strategy'
|
||||
},
|
||||
{
|
||||
input: `measure userTrust(user: User) {
|
||||
aggregate {
|
||||
user.reputation
|
||||
user.activityScore
|
||||
user.verificationLevel
|
||||
user.socialProof
|
||||
} USING min
|
||||
} PROVIDES number`,
|
||||
description: 'Aggregation with min strategy'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-measure-aggregation-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Measure aggregation test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testMeasureFusion() {
|
||||
console.log('Testing Measure Fusion...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `measure effectiveClearance(user: User) {
|
||||
fusion max {
|
||||
user.clearance
|
||||
user.role.clearance
|
||||
user.group.clearance
|
||||
}
|
||||
} PROVIDES string`,
|
||||
description: 'Fusion with max strategy'
|
||||
},
|
||||
{
|
||||
input: `measure userPermissions(user: User) {
|
||||
fusion min {
|
||||
user.role.permissions
|
||||
user.group.permissions
|
||||
}
|
||||
} PROVIDES Permission[]`,
|
||||
description: 'Fusion with min strategy'
|
||||
},
|
||||
{
|
||||
input: `measure userScore(user: User) {
|
||||
fusion majority {
|
||||
user.reputation
|
||||
user.activityScore
|
||||
user.verificationLevel
|
||||
}
|
||||
} PROVIDES number`,
|
||||
description: 'Fusion with majority strategy'
|
||||
},
|
||||
{
|
||||
input: `measure userTrust(user: User) {
|
||||
fusion average {
|
||||
user.reputation
|
||||
user.activityScore
|
||||
user.verificationLevel
|
||||
user.socialProof
|
||||
}
|
||||
} PROVIDES number`,
|
||||
description: 'Fusion with average strategy'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-measure-fusion-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Measure fusion test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testComplexMeasures() {
|
||||
console.log('Testing Complex Measures...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `measure userEffectivePermissions(user: User) {
|
||||
aggregate {
|
||||
user.role.permissions
|
||||
user.group.permissions
|
||||
user.directPermissions
|
||||
} USING majority
|
||||
} PROVIDES Permission[]`,
|
||||
description: 'Complex aggregation with multiple sources'
|
||||
},
|
||||
{
|
||||
input: `measure userTrustScore(user: User) {
|
||||
fusion average {
|
||||
user.reputation
|
||||
user.activityScore
|
||||
user.verificationLevel
|
||||
user.socialProof
|
||||
user.peerRatings
|
||||
}
|
||||
} PROVIDES number`,
|
||||
description: 'Complex fusion with multiple metrics'
|
||||
},
|
||||
{
|
||||
input: `measure userAccessLevel(user: User) {
|
||||
fusion max {
|
||||
user.clearance
|
||||
user.role.clearance
|
||||
user.group.clearance
|
||||
user.temporaryClearance
|
||||
}
|
||||
} PROVIDES string`,
|
||||
description: 'Complex clearance calculation'
|
||||
},
|
||||
{
|
||||
input: `measure userSimilarity(user1: User, user2: User) {
|
||||
similar(user1, user2) |similarity| {
|
||||
similarity
|
||||
} with similarity > 0.5
|
||||
} PROVIDES number`,
|
||||
description: 'Similarity measure with pattern matching'
|
||||
},
|
||||
{
|
||||
input: `measure userEffectiveRole(user: User) {
|
||||
fusion majority {
|
||||
user.role
|
||||
user.temporaryRole
|
||||
user.actingRole
|
||||
}
|
||||
} PROVIDES string`,
|
||||
description: 'Role determination with multiple sources'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-complex-measure-${Date.now()}`);
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} catch (error) {
|
||||
this.fail(`Complex measure test: ${description}`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testMeasureErrors() {
|
||||
console.log('Testing Measure Error Handling...');
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
input: `measure userRole(user: User) {
|
||||
user.role
|
||||
}`,
|
||||
description: 'Missing PROVIDES clause should fail'
|
||||
},
|
||||
{
|
||||
input: `measure userRole(user: User) {
|
||||
user.role
|
||||
} PROVIDES`,
|
||||
description: 'Incomplete PROVIDES clause should fail'
|
||||
},
|
||||
{
|
||||
input: `measure userRole(user: User) {
|
||||
user.role
|
||||
} PROVIDES string`,
|
||||
description: 'Valid measure should succeed'
|
||||
},
|
||||
{
|
||||
input: `measure userPermissions(user: User) {
|
||||
aggregate {
|
||||
user.role.permissions
|
||||
user.group.permissions
|
||||
} USING
|
||||
} PROVIDES Permission[]`,
|
||||
description: 'Incomplete USING clause should fail'
|
||||
},
|
||||
{
|
||||
input: `measure userScore(user: User) {
|
||||
fusion {
|
||||
user.reputation
|
||||
user.activityScore
|
||||
}
|
||||
} PROVIDES number`,
|
||||
description: 'Missing fusion strategy should fail'
|
||||
},
|
||||
{
|
||||
input: `measure userRole(user: User) {
|
||||
invalid syntax here
|
||||
} PROVIDES string`,
|
||||
description: 'Invalid syntax should fail'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
try {
|
||||
const result = this.compiler.compile(input, `test-measure-error-${Date.now()}`);
|
||||
if (description.includes('should succeed')) {
|
||||
this.assert(result.success, `${description} should parse successfully`);
|
||||
console.log(` ✓ ${description}`);
|
||||
} else {
|
||||
this.assert(!result.success, `${description} should fail to parse`);
|
||||
console.log(` ✓ ${description} (correctly failed)`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (description.includes('should succeed')) {
|
||||
this.fail(`Measure error test: ${description}`, error);
|
||||
} else {
|
||||
// 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 runMeasureTests(arbiter) {
|
||||
const test = new MeasureTests();
|
||||
test.setup(arbiter);
|
||||
return test.runAllTests();
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
# Evidence DSL Test Suite
|
||||
|
||||
## Overview
|
||||
|
||||
This comprehensive test suite follows a **structural linguistic approach** to validate the Evidence DSL (Domain Specific Language) for authorization policies. The tests are organized incrementally from basic language primitives to complex integration scenarios.
|
||||
|
||||
## Test Structure
|
||||
|
||||
### 1. Structural Linguistic Tests (`StructuralLinguisticTests.js`)
|
||||
**Level: Comprehensive**
|
||||
- **Lexical Primitives**: Identifiers, literals, keywords, whitespace
|
||||
- **Basic Expressions**: Arithmetic, logical, comparison, temporal
|
||||
- **Type System**: Definitions, fields, behaviors, caching
|
||||
- **Fact System**: Declarations, properties, caching, limits
|
||||
- **Evidence System**: Rules, defeasible logic, pattern matching
|
||||
- **Measure System**: Aggregation, fusion, return types
|
||||
- **Complex Integration**: Multi-feature combinations
|
||||
|
||||
### 2. Expression Tests (`ExpressionTests.js`)
|
||||
**Level: Focused**
|
||||
- Arithmetic operator precedence
|
||||
- Logical operator precedence
|
||||
- Comparison operators
|
||||
- Temporal expressions
|
||||
- Unary operators
|
||||
- Attribute access
|
||||
- Function calls
|
||||
- Complex expressions
|
||||
- Error handling
|
||||
|
||||
### 3. Definition Tests (`DefinitionTests.js`)
|
||||
**Level: Focused**
|
||||
- Basic type definitions
|
||||
- Field types (string, number, boolean, timestamp, custom)
|
||||
- Array types
|
||||
- Behaviors (decay, blur, TTL)
|
||||
- Caching (eager, lazy)
|
||||
- Complex definitions
|
||||
- Error handling
|
||||
|
||||
### 4. Fact Tests (`FactTests.js`)
|
||||
**Level: Focused**
|
||||
- Basic fact declarations
|
||||
- Fact properties (transitive, symmetrical)
|
||||
- Fact caching
|
||||
- Fact limits
|
||||
- Parameter types
|
||||
- Complex facts
|
||||
- Error handling
|
||||
|
||||
### 5. Evidence Tests (`EvidenceTests.js`)
|
||||
**Level: Focused**
|
||||
- Basic evidence rules
|
||||
- Defeasible logic (ALWAYS, WHEN/UNLESS, REQUIRES)
|
||||
- Pattern matching with wildcards
|
||||
- Fusion strategies (min, max, majority, average)
|
||||
- Complex evidence composition
|
||||
- Error handling
|
||||
|
||||
### 6. Measure Tests (`MeasureTests.js`)
|
||||
**Level: Focused**
|
||||
- Basic measure definitions
|
||||
- Return types
|
||||
- Aggregation with different strategies
|
||||
- Fusion with different strategies
|
||||
- Complex measures
|
||||
- Error handling
|
||||
|
||||
### 7. Integration Tests (`IntegrationTests.js`)
|
||||
**Level: Integration**
|
||||
- Complete authorization systems
|
||||
- Multi-domain systems
|
||||
- Hierarchical access patterns
|
||||
- Similarity-based access
|
||||
- Temporal access patterns
|
||||
- Complex behaviors
|
||||
- Performance scenarios
|
||||
|
||||
## Test Runner (`TestRunner.js`)
|
||||
|
||||
The test runner orchestrates all test suites and provides:
|
||||
- **Comprehensive Testing**: Run all test suites
|
||||
- **Selective Testing**: Run specific test suites
|
||||
- **Level-based Testing**: Run tests by complexity level
|
||||
- **Detailed Reporting**: Summary and detailed results
|
||||
- **Coverage Analysis**: Language feature coverage
|
||||
|
||||
## Usage
|
||||
|
||||
### Run All Tests
|
||||
```javascript
|
||||
import { runAllTests } from './tests/TestRunner.js';
|
||||
|
||||
const results = runAllTests(arbiter);
|
||||
console.log(`Tests: ${results.passed}/${results.total} passed`);
|
||||
```
|
||||
|
||||
### Run Specific Test Suites
|
||||
```javascript
|
||||
import { runSpecificTests } from './tests/TestRunner.js';
|
||||
|
||||
const results = runSpecificTests(arbiter, [
|
||||
'Expression Tests',
|
||||
'Definition Tests'
|
||||
]);
|
||||
```
|
||||
|
||||
### Run Tests by Level
|
||||
```javascript
|
||||
import { runTestsByLevel } from './tests/TestRunner.js';
|
||||
|
||||
// Run only focused tests
|
||||
const results = runTestsByLevel(arbiter, 'focused');
|
||||
|
||||
// Run only integration tests
|
||||
const results = runTestsByLevel(arbiter, 'integration');
|
||||
```
|
||||
|
||||
## Language Feature Coverage
|
||||
|
||||
### ✅ Lexical Primitives
|
||||
- Identifiers (simple, with underscores, with numbers)
|
||||
- Literals (string, number, boolean, duration)
|
||||
- Keywords (reserved words)
|
||||
- Whitespace and comments
|
||||
|
||||
### ✅ Expression System
|
||||
- Arithmetic operators (+, -, *, /) with precedence
|
||||
- Logical operators (&&, ||, NOT) with precedence
|
||||
- Comparison operators (==, !=, >, <, >=, <=)
|
||||
- Temporal expressions (within)
|
||||
- Unary operators (NOT, !)
|
||||
- Attribute access (object.attribute)
|
||||
- Function calls (predicate(args))
|
||||
|
||||
### ✅ Type System
|
||||
- Type definitions with fields
|
||||
- Field types (string, number, boolean, timestamp, custom)
|
||||
- Array types (Type[])
|
||||
- Behaviors (decay, blur, TTL)
|
||||
- Caching directives (eager, lazy)
|
||||
|
||||
### ✅ Fact System
|
||||
- Fact declarations with parameters
|
||||
- Fact properties (transitive, symmetrical)
|
||||
- Caching directives
|
||||
- Limits for performance
|
||||
- Parameter types
|
||||
|
||||
### ✅ Evidence System
|
||||
- Basic evidence rules
|
||||
- Defeasible logic (ALWAYS, WHEN/UNLESS, REQUIRES)
|
||||
- Pattern matching with wildcards (*)
|
||||
- Binding clauses (|variable|)
|
||||
- With clauses (with condition)
|
||||
- Limits for pattern matching
|
||||
- Fusion strategies (min, max, majority, average)
|
||||
|
||||
### ✅ Measure System
|
||||
- Measure definitions
|
||||
- Return type specifications (PROVIDES)
|
||||
- Aggregation with strategies (USING)
|
||||
- Fusion with strategies
|
||||
- Complex value computation
|
||||
|
||||
### ✅ Integration Features
|
||||
- Multi-domain systems
|
||||
- Hierarchical access patterns
|
||||
- Similarity-based access
|
||||
- Temporal access patterns
|
||||
- Complex behavior combinations
|
||||
- Performance optimization scenarios
|
||||
|
||||
## Test Philosophy
|
||||
|
||||
### Structural Linguistic Approach
|
||||
The tests follow a structural linguistic methodology:
|
||||
|
||||
1. **Phonological Level**: Basic lexical elements (identifiers, literals)
|
||||
2. **Morphological Level**: Word formation (operators, keywords)
|
||||
3. **Syntactic Level**: Grammar rules (expressions, statements)
|
||||
4. **Semantic Level**: Meaning (types, behaviors, logic)
|
||||
5. **Pragmatic Level**: Usage (integration, real-world scenarios)
|
||||
|
||||
### Incremental Complexity
|
||||
Tests progress from simple to complex:
|
||||
- **Level 1**: Lexical primitives
|
||||
- **Level 2**: Basic expressions
|
||||
- **Level 3**: Type system
|
||||
- **Level 4**: Fact system
|
||||
- **Level 5**: Evidence system
|
||||
- **Level 6**: Measure system
|
||||
- **Level 7**: Complex integration
|
||||
|
||||
### Comprehensive Coverage
|
||||
Each language feature is tested for:
|
||||
- **Valid cases**: Correct syntax and semantics
|
||||
- **Invalid cases**: Error handling and recovery
|
||||
- **Edge cases**: Boundary conditions
|
||||
- **Integration**: Multi-feature combinations
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Prerequisites
|
||||
- Node.js environment
|
||||
- Arbiter instance for testing
|
||||
- All dependencies installed
|
||||
|
||||
### Basic Usage
|
||||
```bash
|
||||
# Run all tests
|
||||
npm test
|
||||
|
||||
# Run specific test file
|
||||
node src/ast/tests/StructuralLinguisticTests.js
|
||||
|
||||
# Run with specific arbiter
|
||||
node -e "
|
||||
import { runAllTests } from './src/ast/tests/TestRunner.js';
|
||||
const results = runAllTests(arbiter);
|
||||
console.log(results);
|
||||
"
|
||||
```
|
||||
|
||||
### Test Output
|
||||
The test runner provides:
|
||||
- **Progress indicators**: Real-time test execution
|
||||
- **Detailed results**: Pass/fail status for each test
|
||||
- **Error reporting**: Specific error messages for failures
|
||||
- **Performance metrics**: Execution time for each suite
|
||||
- **Coverage analysis**: Language feature coverage
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new tests:
|
||||
1. Follow the structural linguistic approach
|
||||
2. Test both valid and invalid cases
|
||||
3. Include error handling tests
|
||||
4. Document test purpose and expected behavior
|
||||
5. Maintain incremental complexity
|
||||
6. Update coverage documentation
|
||||
|
||||
## Test Maintenance
|
||||
|
||||
- **Regular Updates**: Keep tests current with language changes
|
||||
- **Performance Monitoring**: Track test execution time
|
||||
- **Coverage Analysis**: Ensure comprehensive feature coverage
|
||||
- **Error Handling**: Validate error messages and recovery
|
||||
- **Integration Testing**: Test real-world scenarios
|
||||
@@ -1,413 +0,0 @@
|
||||
/**
|
||||
* Comprehensive Test Runner for Evidence DSL
|
||||
*
|
||||
* Orchestrates all test suites in a structural linguistic approach,
|
||||
* from basic primitives to complex integration scenarios.
|
||||
*/
|
||||
|
||||
import { runStructuralLinguisticTests } from './StructuralLinguisticTests.js';
|
||||
import { runExpressionTests } from './ExpressionTests.js';
|
||||
import { runDefinitionTests } from './DefinitionTests.js';
|
||||
import { runFactTests } from './FactTests.js';
|
||||
import { runEvidenceTests } from './EvidenceTests.js';
|
||||
import { runMeasureTests } from './MeasureTests.js';
|
||||
import { runIntegrationTests } from './IntegrationTests.js';
|
||||
|
||||
export class TestRunner {
|
||||
constructor() {
|
||||
this.arbiter = null;
|
||||
this.testSuites = [];
|
||||
this.results = {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
success: false,
|
||||
suites: []
|
||||
};
|
||||
}
|
||||
|
||||
setup(arbiter) {
|
||||
this.arbiter = arbiter;
|
||||
this.testSuites = [
|
||||
{
|
||||
name: 'Structural Linguistic Tests',
|
||||
description: 'Comprehensive tests from basic primitives to complex features',
|
||||
runner: runStructuralLinguisticTests,
|
||||
level: 'comprehensive'
|
||||
},
|
||||
{
|
||||
name: 'Expression Tests',
|
||||
description: 'Expression parsing, operator precedence, and complex expressions',
|
||||
runner: runExpressionTests,
|
||||
level: 'focused'
|
||||
},
|
||||
{
|
||||
name: 'Definition Tests',
|
||||
description: 'Type definitions, fields, behaviors, and caching',
|
||||
runner: runDefinitionTests,
|
||||
level: 'focused'
|
||||
},
|
||||
{
|
||||
name: 'Fact Tests',
|
||||
description: 'Fact declarations, properties, and caching',
|
||||
runner: runFactTests,
|
||||
level: 'focused'
|
||||
},
|
||||
{
|
||||
name: 'Evidence Tests',
|
||||
description: 'Evidence rules, defeasible logic, and pattern matching',
|
||||
runner: runEvidenceTests,
|
||||
level: 'focused'
|
||||
},
|
||||
{
|
||||
name: 'Measure Tests',
|
||||
description: 'Measure definitions, aggregation, and fusion',
|
||||
runner: runMeasureTests,
|
||||
level: 'focused'
|
||||
},
|
||||
{
|
||||
name: 'Integration Tests',
|
||||
description: 'Complex multi-feature integration scenarios',
|
||||
runner: runIntegrationTests,
|
||||
level: 'integration'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all test suites
|
||||
* @returns {Object} Comprehensive test results
|
||||
*/
|
||||
runAllTests() {
|
||||
console.log('='.repeat(80));
|
||||
console.log('EVIDENCE DSL COMPREHENSIVE TEST SUITE');
|
||||
console.log('='.repeat(80));
|
||||
console.log('Structural Linguistic Approach: Testing from primitives to integration\n');
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
for (const suite of this.testSuites) {
|
||||
console.log(`\n${'='.repeat(60)}`);
|
||||
console.log(`Running: ${suite.name}`);
|
||||
console.log(`Level: ${suite.level.toUpperCase()}`);
|
||||
console.log(`Description: ${suite.description}`);
|
||||
console.log(`${'='.repeat(60)}`);
|
||||
|
||||
try {
|
||||
const suiteStartTime = Date.now();
|
||||
const suiteResults = suite.runner(this.arbiter);
|
||||
const suiteEndTime = Date.now();
|
||||
const suiteDuration = suiteEndTime - suiteStartTime;
|
||||
|
||||
this.results.suites.push({
|
||||
name: suite.name,
|
||||
level: suite.level,
|
||||
duration: suiteDuration,
|
||||
results: suiteResults
|
||||
});
|
||||
|
||||
this.results.total += suiteResults.total;
|
||||
this.results.passed += suiteResults.passed;
|
||||
this.results.failed += suiteResults.failed;
|
||||
|
||||
console.log(`\n${suite.name} completed in ${suiteDuration}ms`);
|
||||
console.log(`Results: ${suiteResults.passed}/${suiteResults.total} passed, ${suiteResults.failed} failed`);
|
||||
|
||||
if (suiteResults.success) {
|
||||
console.log(`✓ ${suite.name} PASSED`);
|
||||
} else {
|
||||
console.log(`✗ ${suite.name} FAILED`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`\n✗ ${suite.name} ERROR: ${error.message}`);
|
||||
this.results.suites.push({
|
||||
name: suite.name,
|
||||
level: suite.level,
|
||||
duration: 0,
|
||||
results: {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 1,
|
||||
success: false,
|
||||
error: error.message
|
||||
}
|
||||
});
|
||||
this.results.failed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const endTime = Date.now();
|
||||
const totalDuration = endTime - startTime;
|
||||
|
||||
this.results.success = this.results.failed === 0;
|
||||
|
||||
this.printSummary(totalDuration);
|
||||
this.printDetailedResults();
|
||||
|
||||
return this.results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run specific test suites
|
||||
* @param {string[]} suiteNames - Names of test suites to run
|
||||
* @returns {Object} Test results for specified suites
|
||||
*/
|
||||
runSpecificTests(suiteNames) {
|
||||
console.log('='.repeat(80));
|
||||
console.log('EVIDENCE DSL SELECTIVE TEST SUITE');
|
||||
console.log('='.repeat(80));
|
||||
console.log(`Running: ${suiteNames.join(', ')}\n`);
|
||||
|
||||
const startTime = Date.now();
|
||||
const selectedSuites = this.testSuites.filter(suite => suiteNames.includes(suite.name));
|
||||
|
||||
for (const suite of selectedSuites) {
|
||||
console.log(`\n${'='.repeat(60)}`);
|
||||
console.log(`Running: ${suite.name}`);
|
||||
console.log(`${'='.repeat(60)}`);
|
||||
|
||||
try {
|
||||
const suiteStartTime = Date.now();
|
||||
const suiteResults = suite.runner(this.arbiter);
|
||||
const suiteEndTime = Date.now();
|
||||
const suiteDuration = suiteEndTime - suiteStartTime;
|
||||
|
||||
this.results.suites.push({
|
||||
name: suite.name,
|
||||
level: suite.level,
|
||||
duration: suiteDuration,
|
||||
results: suiteResults
|
||||
});
|
||||
|
||||
this.results.total += suiteResults.total;
|
||||
this.results.passed += suiteResults.passed;
|
||||
this.results.failed += suiteResults.failed;
|
||||
|
||||
console.log(`\n${suite.name} completed in ${suiteDuration}ms`);
|
||||
console.log(`Results: ${suiteResults.passed}/${suiteResults.total} passed, ${suiteResults.failed} failed`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`\n✗ ${suite.name} ERROR: ${error.message}`);
|
||||
this.results.failed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const endTime = Date.now();
|
||||
const totalDuration = endTime - startTime;
|
||||
|
||||
this.results.success = this.results.failed === 0;
|
||||
this.printSummary(totalDuration);
|
||||
|
||||
return this.results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tests by level
|
||||
* @param {string} level - Test level to run ('comprehensive', 'focused', 'integration')
|
||||
* @returns {Object} Test results for specified level
|
||||
*/
|
||||
runTestsByLevel(level) {
|
||||
const levelSuites = this.testSuites.filter(suite => suite.level === level);
|
||||
const suiteNames = levelSuites.map(suite => suite.name);
|
||||
return this.runSpecificTests(suiteNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print test summary
|
||||
* @param {number} totalDuration - Total test duration in milliseconds
|
||||
*/
|
||||
printSummary(totalDuration) {
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('TEST SUMMARY');
|
||||
console.log('='.repeat(80));
|
||||
console.log(`Total Tests: ${this.results.total}`);
|
||||
console.log(`Passed: ${this.results.passed}`);
|
||||
console.log(`Failed: ${this.results.failed}`);
|
||||
console.log(`Success Rate: ${((this.results.passed / this.results.total) * 100).toFixed(2)}%`);
|
||||
console.log(`Total Duration: ${totalDuration}ms`);
|
||||
console.log(`Status: ${this.results.success ? '✓ ALL TESTS PASSED' : '✗ SOME TESTS FAILED'}`);
|
||||
console.log('='.repeat(80));
|
||||
}
|
||||
|
||||
/**
|
||||
* Print detailed results for each test suite
|
||||
*/
|
||||
printDetailedResults() {
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('DETAILED RESULTS');
|
||||
console.log('='.repeat(80));
|
||||
|
||||
this.results.suites.forEach(suite => {
|
||||
console.log(`\n${suite.name} (${suite.level}):`);
|
||||
console.log(` Duration: ${suite.duration}ms`);
|
||||
console.log(` Total: ${suite.results.total}`);
|
||||
console.log(` Passed: ${suite.results.passed}`);
|
||||
console.log(` Failed: ${suite.results.failed}`);
|
||||
console.log(` Success: ${suite.results.success ? '✓' : '✗'}`);
|
||||
|
||||
if (suite.results.error) {
|
||||
console.log(` Error: ${suite.results.error}`);
|
||||
}
|
||||
|
||||
if (suite.results.results && suite.results.results.length > 0) {
|
||||
console.log(' Individual Results:');
|
||||
suite.results.results.forEach(result => {
|
||||
const status = result.status === 'PASSED' ? '✓' : '✗';
|
||||
console.log(` ${status} ${result.test}`);
|
||||
if (result.error) {
|
||||
console.log(` Error: ${result.error}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get test coverage report
|
||||
* @returns {Object} Coverage report
|
||||
*/
|
||||
getCoverageReport() {
|
||||
const coverage = {
|
||||
lexical: {
|
||||
identifiers: 'tested',
|
||||
literals: 'tested',
|
||||
keywords: 'tested',
|
||||
whitespace: 'tested'
|
||||
},
|
||||
expressions: {
|
||||
arithmetic: 'tested',
|
||||
logical: 'tested',
|
||||
comparison: 'tested',
|
||||
temporal: 'tested',
|
||||
unary: 'tested',
|
||||
attributeAccess: 'tested',
|
||||
functionCalls: 'tested'
|
||||
},
|
||||
types: {
|
||||
definitions: 'tested',
|
||||
fields: 'tested',
|
||||
behaviors: 'tested',
|
||||
caching: 'tested',
|
||||
arrays: 'tested'
|
||||
},
|
||||
facts: {
|
||||
declarations: 'tested',
|
||||
properties: 'tested',
|
||||
caching: 'tested',
|
||||
limits: 'tested',
|
||||
parameters: 'tested'
|
||||
},
|
||||
evidence: {
|
||||
basic: 'tested',
|
||||
defeasibleLogic: 'tested',
|
||||
patternMatching: 'tested',
|
||||
fusion: 'tested',
|
||||
complex: 'tested'
|
||||
},
|
||||
measures: {
|
||||
basic: 'tested',
|
||||
aggregation: 'tested',
|
||||
fusion: 'tested',
|
||||
returnTypes: 'tested',
|
||||
complex: 'tested'
|
||||
},
|
||||
integration: {
|
||||
completeSystems: 'tested',
|
||||
multiDomain: 'tested',
|
||||
hierarchical: 'tested',
|
||||
similarity: 'tested',
|
||||
temporal: 'tested',
|
||||
behaviors: 'tested',
|
||||
performance: 'tested'
|
||||
}
|
||||
};
|
||||
|
||||
return coverage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language feature coverage
|
||||
* @returns {Object} Feature coverage report
|
||||
*/
|
||||
getFeatureCoverage() {
|
||||
return {
|
||||
languagePrimitives: {
|
||||
identifiers: '✓',
|
||||
literals: '✓',
|
||||
keywords: '✓',
|
||||
operators: '✓',
|
||||
expressions: '✓'
|
||||
},
|
||||
typeSystem: {
|
||||
definitions: '✓',
|
||||
fields: '✓',
|
||||
behaviors: '✓',
|
||||
caching: '✓',
|
||||
arrays: '✓'
|
||||
},
|
||||
factSystem: {
|
||||
declarations: '✓',
|
||||
properties: '✓',
|
||||
caching: '✓',
|
||||
limits: '✓',
|
||||
parameters: '✓'
|
||||
},
|
||||
evidenceSystem: {
|
||||
rules: '✓',
|
||||
defeasibleLogic: '✓',
|
||||
patternMatching: '✓',
|
||||
fusion: '✓',
|
||||
complex: '✓'
|
||||
},
|
||||
measureSystem: {
|
||||
definitions: '✓',
|
||||
aggregation: '✓',
|
||||
fusion: '✓',
|
||||
returnTypes: '✓',
|
||||
complex: '✓'
|
||||
},
|
||||
integration: {
|
||||
multiFeature: '✓',
|
||||
realWorld: '✓',
|
||||
performance: '✓',
|
||||
scalability: '✓'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all tests
|
||||
* @param {Object} arbiter - Arbiter instance for testing
|
||||
* @returns {Object} Comprehensive test results
|
||||
*/
|
||||
export function runAllTests(arbiter) {
|
||||
const runner = new TestRunner();
|
||||
runner.setup(arbiter);
|
||||
return runner.runAllTests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run specific test suites
|
||||
* @param {Object} arbiter - Arbiter instance for testing
|
||||
* @param {string[]} suiteNames - Names of test suites to run
|
||||
* @returns {Object} Test results for specified suites
|
||||
*/
|
||||
export function runSpecificTests(arbiter, suiteNames) {
|
||||
const runner = new TestRunner();
|
||||
runner.setup(arbiter);
|
||||
return runner.runSpecificTests(suiteNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run tests by level
|
||||
* @param {Object} arbiter - Arbiter instance for testing
|
||||
* @param {string} level - Test level to run
|
||||
* @returns {Object} Test results for specified level
|
||||
*/
|
||||
export function runTestsByLevel(arbiter, level) {
|
||||
const runner = new TestRunner();
|
||||
runner.setup(arbiter);
|
||||
return runner.runTestsByLevel(level);
|
||||
}
|
||||
@@ -579,17 +579,6 @@ export class QualitativeRelationalComparatorRule extends BaseRule {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use _aggregateCrispValues instead. Removal after Stage 2.
|
||||
*/
|
||||
_aggregateBlurredValues(blurredValues, operandConfig, scale) {
|
||||
if (!this._warnedAggregateBlurredValues) {
|
||||
this._warnedAggregateBlurredValues = true;
|
||||
console.warn('[QualitativeRelationalComparatorRule] _aggregateBlurredValues is deprecated; use _aggregateCrispValues instead.');
|
||||
}
|
||||
return this._aggregateCrispValues(blurredValues, operandConfig, scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare blurred qualitative intervals
|
||||
* @private
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { RelationCSR } from './relation/RelationCSR.js';
|
||||
import { RelationSnapshotAccess } from './relation/RelationSnapshotAccess.js';
|
||||
import { RelationCaches } from './relation/RelationCaches.js';
|
||||
import { RelationLookup } from './relation/RelationLookup.js';
|
||||
@@ -22,16 +21,10 @@ export class RelationManager {
|
||||
this._relationKeyToIndex = new Map();
|
||||
this._relationNameToId = new Map();
|
||||
|
||||
this._relationCsr = new RelationCSR(this);
|
||||
this._snapshotAccess = new RelationSnapshotAccess(this);
|
||||
this._lookup = new RelationLookup(this);
|
||||
this._updates = new RelationUpdates(this);
|
||||
this._relationGraph = new RelationGraphTraversal(this);
|
||||
if (this._relationCsr.enabled && !RelationManager._warnedRelationCsrDeprecated) {
|
||||
RelationManager._warnedRelationCsrDeprecated = true;
|
||||
console.warn('[RelationManager] relation CSR index is deprecated; avoid useRelationCsrIndex and related options.');
|
||||
}
|
||||
|
||||
this._cacheHits = 0;
|
||||
this._cacheMisses = 0;
|
||||
|
||||
@@ -60,26 +53,6 @@ export class RelationManager {
|
||||
return this._relationGraph.shouldUseTraversal(nodeId, relation, reverse);
|
||||
}
|
||||
|
||||
_buildRelationCsrIndex(relation) {
|
||||
return this._relationCsr.buildIndex(relation);
|
||||
}
|
||||
|
||||
_getRelationCsrIndex(relation) {
|
||||
return this._relationCsr.getIndex(relation);
|
||||
}
|
||||
|
||||
_recordRelationCsrAdd(relationObj) {
|
||||
this._relationCsr.recordAdd(relationObj);
|
||||
}
|
||||
|
||||
_recordRelationCsrRemove(srcId, relation, dstId) {
|
||||
this._relationCsr.recordRemove(srcId, relation, dstId);
|
||||
}
|
||||
|
||||
_getRelationsFromCsr(csr, nodeId, reverse = false) {
|
||||
return this._relationCsr.getRelationsFromCsr(csr, nodeId, reverse);
|
||||
}
|
||||
|
||||
_getRelationDegreeFromIndices(nodeId, relation, reverse = false) {
|
||||
this._ensureIndicesBuilt();
|
||||
const indices = this.arbiter.indices;
|
||||
@@ -525,17 +498,6 @@ export class RelationManager {
|
||||
return this.arbiter.valueManager.aggregateCrispValues(intervalValues, aggregator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getAggregatedIntervalValue instead. Removal after Stage 2.
|
||||
*/
|
||||
getAggregatedBlurredValue(srcId, relation, aggregator = 'max') {
|
||||
if (!this._warnedAggregatedBlurredValue) {
|
||||
this._warnedAggregatedBlurredValue = true;
|
||||
console.warn('[RelationManager] getAggregatedBlurredValue is deprecated; use getAggregatedIntervalValue instead.');
|
||||
}
|
||||
return this.getAggregatedIntervalValue(srcId, relation, aggregator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare values between two relations using interval arithmetic
|
||||
* @param {Object} leftRelation - Left relation object or { srcId, relation, dstId }
|
||||
@@ -747,7 +709,6 @@ export class RelationManager {
|
||||
|
||||
_clearAllCaches() {
|
||||
this._caches.clearAll();
|
||||
this._relationCsr.clear();
|
||||
this._relationGraph.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -1169,17 +1169,6 @@ export class ValueManager {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use aggregateCrispValues instead. Removal after Stage 2.
|
||||
*/
|
||||
aggregateBlurredValues(blurredValues, aggregator = 'max') {
|
||||
if (!this._warnedAggregateBlurredValues) {
|
||||
this._warnedAggregateBlurredValues = true;
|
||||
console.warn('[ValueManager] aggregateBlurredValues is deprecated; use aggregateCrispValues instead.');
|
||||
}
|
||||
return this.aggregateCrispValues(blurredValues, aggregator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two intervals using a comparator
|
||||
* @param {Object} leftInterval - { min, max }
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
export class RelationCSR {
|
||||
constructor(manager) {
|
||||
this.manager = manager;
|
||||
const options = manager.arbiter?.options || {};
|
||||
this.enabled = !!options.useRelationCsrIndex;
|
||||
this.deltaThreshold = Number.isFinite(options.relationCsrDeltaThreshold)
|
||||
? options.relationCsrDeltaThreshold
|
||||
: 1000;
|
||||
this.minDegree = Number.isFinite(options.relationCsrMinDegree)
|
||||
? options.relationCsrMinDegree
|
||||
: 200;
|
||||
this.byName = new Map();
|
||||
}
|
||||
|
||||
buildIndex(relation) {
|
||||
const relSet = this.manager.arbiter.indices?.relationsByRel?.get(relation);
|
||||
const relations = relSet ? Array.from(relSet) : [];
|
||||
const bySrcMap = new Map();
|
||||
const byDstMap = new Map();
|
||||
|
||||
for (const rel of relations) {
|
||||
let srcList = bySrcMap.get(rel.src);
|
||||
if (!srcList) {
|
||||
srcList = [];
|
||||
bySrcMap.set(rel.src, srcList);
|
||||
}
|
||||
srcList.push(rel);
|
||||
|
||||
let dstList = byDstMap.get(rel.dst);
|
||||
if (!dstList) {
|
||||
dstList = [];
|
||||
byDstMap.set(rel.dst, dstList);
|
||||
}
|
||||
dstList.push(rel);
|
||||
}
|
||||
|
||||
const bySrcEntries = [];
|
||||
const bySrcOffsets = new Map();
|
||||
for (const [srcId, list] of bySrcMap.entries()) {
|
||||
const start = bySrcEntries.length;
|
||||
for (const rel of list) bySrcEntries.push(rel);
|
||||
bySrcOffsets.set(srcId, { start, end: bySrcEntries.length });
|
||||
}
|
||||
|
||||
const byDstEntries = [];
|
||||
const byDstOffsets = new Map();
|
||||
for (const [dstId, list] of byDstMap.entries()) {
|
||||
const start = byDstEntries.length;
|
||||
for (const rel of list) byDstEntries.push(rel);
|
||||
byDstOffsets.set(dstId, { start, end: byDstEntries.length });
|
||||
}
|
||||
|
||||
return {
|
||||
relation,
|
||||
bySrcEntries,
|
||||
bySrcOffsets,
|
||||
byDstEntries,
|
||||
byDstOffsets,
|
||||
addDeltaBySrc: new Map(),
|
||||
addDeltaByDst: new Map(),
|
||||
removeDelta: new Set(),
|
||||
deltaCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
getIndex(relation) {
|
||||
if (!this.enabled) return null;
|
||||
let csr = this.byName.get(relation);
|
||||
if (!csr || csr.deltaCount > this.deltaThreshold) {
|
||||
csr = this.buildIndex(relation);
|
||||
this.byName.set(relation, csr);
|
||||
}
|
||||
return csr;
|
||||
}
|
||||
|
||||
recordAdd(relationObj) {
|
||||
if (!this.enabled) return;
|
||||
const csr = this.byName.get(relationObj.rel);
|
||||
if (!csr) return;
|
||||
const key = this.manager._makeRelationKey(relationObj.src, relationObj.rel, relationObj.dst);
|
||||
if (csr.removeDelta.has(key)) {
|
||||
csr.removeDelta.delete(key);
|
||||
}
|
||||
|
||||
let srcList = csr.addDeltaBySrc.get(relationObj.src);
|
||||
if (!srcList) {
|
||||
srcList = [];
|
||||
csr.addDeltaBySrc.set(relationObj.src, srcList);
|
||||
}
|
||||
srcList.push(relationObj);
|
||||
|
||||
let dstList = csr.addDeltaByDst.get(relationObj.dst);
|
||||
if (!dstList) {
|
||||
dstList = [];
|
||||
csr.addDeltaByDst.set(relationObj.dst, dstList);
|
||||
}
|
||||
dstList.push(relationObj);
|
||||
|
||||
csr.deltaCount++;
|
||||
}
|
||||
|
||||
recordRemove(srcId, relation, dstId) {
|
||||
if (!this.enabled) return;
|
||||
const csr = this.byName.get(relation);
|
||||
if (!csr) return;
|
||||
const key = this.manager._makeRelationKey(srcId, relation, dstId);
|
||||
csr.removeDelta.add(key);
|
||||
csr.deltaCount++;
|
||||
}
|
||||
|
||||
getRelationsFromCsr(csr, nodeId, reverse = false) {
|
||||
const entries = reverse ? csr.byDstEntries : csr.bySrcEntries;
|
||||
const offsets = reverse ? csr.byDstOffsets : csr.bySrcOffsets;
|
||||
const deltaMap = reverse ? csr.addDeltaByDst : csr.addDeltaBySrc;
|
||||
const range = offsets.get(nodeId);
|
||||
const removeDelta = csr.removeDelta;
|
||||
|
||||
const result = [];
|
||||
if (range) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
const rel = entries[i];
|
||||
const key = this.manager._makeRelationKey(rel.src, rel.rel, rel.dst);
|
||||
if (!removeDelta.has(key)) {
|
||||
result.push(rel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const delta = deltaMap.get(nodeId);
|
||||
if (delta && delta.length) {
|
||||
for (const rel of delta) {
|
||||
const key = this.manager._makeRelationKey(rel.src, rel.rel, rel.dst);
|
||||
if (!removeDelta.has(key)) {
|
||||
result.push(rel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getRelationsForNode(nodeId, relation, reverse = false) {
|
||||
if (!this.enabled) return null;
|
||||
const degree = this.manager._getRelationDegreeFromIndices(nodeId, relation, reverse);
|
||||
if (degree < this.minDegree) return null;
|
||||
const csr = this.getIndex(relation);
|
||||
if (!csr) return null;
|
||||
return this.getRelationsFromCsr(csr, nodeId, reverse);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.byName.clear();
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,6 @@ export class RelationLookup {
|
||||
const persistent = this.manager.arbiter.indices.getRelationsFromSrc(srcId, relation);
|
||||
return this.manager._mergeRelationLists(persistent, partial);
|
||||
}
|
||||
const csrResult = this.manager._relationCsr.getRelationsForNode(srcId, relation, false);
|
||||
if (csrResult !== null) {
|
||||
return csrResult;
|
||||
}
|
||||
// Try cache first for frequently accessed patterns - use numeric key
|
||||
const cacheKey = this.manager._makeSrcRelCacheKey(srcId, relation);
|
||||
const cached = this.manager._caches.relationLookupCache.get(cacheKey);
|
||||
@@ -84,10 +80,6 @@ export class RelationLookup {
|
||||
const persistent = this.manager.arbiter.indices.getRelationsToDst(dstId, relation);
|
||||
return this.manager._mergeRelationLists(persistent, partial);
|
||||
}
|
||||
const csrResult = this.manager._relationCsr.getRelationsForNode(dstId, relation, true);
|
||||
if (csrResult !== null) {
|
||||
return csrResult;
|
||||
}
|
||||
// Try cache first - use numeric key
|
||||
const cacheKey = this.manager._makeDstRelCacheKey(dstId, relation);
|
||||
const cached = this.manager._caches.relationLookupCache.get(cacheKey);
|
||||
|
||||
@@ -71,7 +71,6 @@ export class RelationUpdates {
|
||||
this.manager.arbiter.indicesBuilt = true;
|
||||
|
||||
this.manager._addRelationToGraph(srcId, relation, dstId);
|
||||
this.manager._recordRelationCsrAdd(relationObj);
|
||||
|
||||
// Update PLTC indices incrementally (if initialized and not in batch mode)
|
||||
if (!this.manager.arbiter.batchUpdateInProgress) {
|
||||
@@ -124,7 +123,6 @@ export class RelationUpdates {
|
||||
this.manager._invalidateValueRelationCaches(srcId, dstId, relationName);
|
||||
|
||||
this.manager._removeRelationFromGraph(srcId, relationName, dstId);
|
||||
this.manager._recordRelationCsrRemove(srcId, relationName, dstId);
|
||||
|
||||
// Notify ValueManager about the removed relation's last state
|
||||
if (relationObjectToRemove.value !== undefined && relationObjectToRemove.stateId) {
|
||||
@@ -368,7 +366,6 @@ export class RelationUpdates {
|
||||
this.manager.arbiter.indicesBuilt = true;
|
||||
|
||||
this.manager._addRelationToGraph(srcId, relation, dstId);
|
||||
this.manager._recordRelationCsrAdd(relationObj);
|
||||
|
||||
// Update PLTC indices incrementally (if initialized and not in batch mode)
|
||||
// For updates, only update if it's a new relation (not an existing one being modified)
|
||||
|
||||
Reference in New Issue
Block a user