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
|
* Compare blurred qualitative intervals
|
||||||
* @private
|
* @private
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { RelationCSR } from './relation/RelationCSR.js';
|
|
||||||
import { RelationSnapshotAccess } from './relation/RelationSnapshotAccess.js';
|
import { RelationSnapshotAccess } from './relation/RelationSnapshotAccess.js';
|
||||||
import { RelationCaches } from './relation/RelationCaches.js';
|
import { RelationCaches } from './relation/RelationCaches.js';
|
||||||
import { RelationLookup } from './relation/RelationLookup.js';
|
import { RelationLookup } from './relation/RelationLookup.js';
|
||||||
@@ -22,16 +21,10 @@ export class RelationManager {
|
|||||||
this._relationKeyToIndex = new Map();
|
this._relationKeyToIndex = new Map();
|
||||||
this._relationNameToId = new Map();
|
this._relationNameToId = new Map();
|
||||||
|
|
||||||
this._relationCsr = new RelationCSR(this);
|
|
||||||
this._snapshotAccess = new RelationSnapshotAccess(this);
|
this._snapshotAccess = new RelationSnapshotAccess(this);
|
||||||
this._lookup = new RelationLookup(this);
|
this._lookup = new RelationLookup(this);
|
||||||
this._updates = new RelationUpdates(this);
|
this._updates = new RelationUpdates(this);
|
||||||
this._relationGraph = new RelationGraphTraversal(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._cacheHits = 0;
|
||||||
this._cacheMisses = 0;
|
this._cacheMisses = 0;
|
||||||
|
|
||||||
@@ -60,26 +53,6 @@ export class RelationManager {
|
|||||||
return this._relationGraph.shouldUseTraversal(nodeId, relation, reverse);
|
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) {
|
_getRelationDegreeFromIndices(nodeId, relation, reverse = false) {
|
||||||
this._ensureIndicesBuilt();
|
this._ensureIndicesBuilt();
|
||||||
const indices = this.arbiter.indices;
|
const indices = this.arbiter.indices;
|
||||||
@@ -525,17 +498,6 @@ export class RelationManager {
|
|||||||
return this.arbiter.valueManager.aggregateCrispValues(intervalValues, aggregator);
|
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
|
* Compare values between two relations using interval arithmetic
|
||||||
* @param {Object} leftRelation - Left relation object or { srcId, relation, dstId }
|
* @param {Object} leftRelation - Left relation object or { srcId, relation, dstId }
|
||||||
@@ -747,7 +709,6 @@ export class RelationManager {
|
|||||||
|
|
||||||
_clearAllCaches() {
|
_clearAllCaches() {
|
||||||
this._caches.clearAll();
|
this._caches.clearAll();
|
||||||
this._relationCsr.clear();
|
|
||||||
this._relationGraph.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
|
* Compare two intervals using a comparator
|
||||||
* @param {Object} leftInterval - { min, max }
|
* @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);
|
const persistent = this.manager.arbiter.indices.getRelationsFromSrc(srcId, relation);
|
||||||
return this.manager._mergeRelationLists(persistent, partial);
|
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
|
// Try cache first for frequently accessed patterns - use numeric key
|
||||||
const cacheKey = this.manager._makeSrcRelCacheKey(srcId, relation);
|
const cacheKey = this.manager._makeSrcRelCacheKey(srcId, relation);
|
||||||
const cached = this.manager._caches.relationLookupCache.get(cacheKey);
|
const cached = this.manager._caches.relationLookupCache.get(cacheKey);
|
||||||
@@ -84,10 +80,6 @@ export class RelationLookup {
|
|||||||
const persistent = this.manager.arbiter.indices.getRelationsToDst(dstId, relation);
|
const persistent = this.manager.arbiter.indices.getRelationsToDst(dstId, relation);
|
||||||
return this.manager._mergeRelationLists(persistent, partial);
|
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
|
// Try cache first - use numeric key
|
||||||
const cacheKey = this.manager._makeDstRelCacheKey(dstId, relation);
|
const cacheKey = this.manager._makeDstRelCacheKey(dstId, relation);
|
||||||
const cached = this.manager._caches.relationLookupCache.get(cacheKey);
|
const cached = this.manager._caches.relationLookupCache.get(cacheKey);
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ export class RelationUpdates {
|
|||||||
this.manager.arbiter.indicesBuilt = true;
|
this.manager.arbiter.indicesBuilt = true;
|
||||||
|
|
||||||
this.manager._addRelationToGraph(srcId, relation, dstId);
|
this.manager._addRelationToGraph(srcId, relation, dstId);
|
||||||
this.manager._recordRelationCsrAdd(relationObj);
|
|
||||||
|
|
||||||
// Update PLTC indices incrementally (if initialized and not in batch mode)
|
// Update PLTC indices incrementally (if initialized and not in batch mode)
|
||||||
if (!this.manager.arbiter.batchUpdateInProgress) {
|
if (!this.manager.arbiter.batchUpdateInProgress) {
|
||||||
@@ -124,7 +123,6 @@ export class RelationUpdates {
|
|||||||
this.manager._invalidateValueRelationCaches(srcId, dstId, relationName);
|
this.manager._invalidateValueRelationCaches(srcId, dstId, relationName);
|
||||||
|
|
||||||
this.manager._removeRelationFromGraph(srcId, relationName, dstId);
|
this.manager._removeRelationFromGraph(srcId, relationName, dstId);
|
||||||
this.manager._recordRelationCsrRemove(srcId, relationName, dstId);
|
|
||||||
|
|
||||||
// Notify ValueManager about the removed relation's last state
|
// Notify ValueManager about the removed relation's last state
|
||||||
if (relationObjectToRemove.value !== undefined && relationObjectToRemove.stateId) {
|
if (relationObjectToRemove.value !== undefined && relationObjectToRemove.stateId) {
|
||||||
@@ -368,7 +366,6 @@ export class RelationUpdates {
|
|||||||
this.manager.arbiter.indicesBuilt = true;
|
this.manager.arbiter.indicesBuilt = true;
|
||||||
|
|
||||||
this.manager._addRelationToGraph(srcId, relation, dstId);
|
this.manager._addRelationToGraph(srcId, relation, dstId);
|
||||||
this.manager._recordRelationCsrAdd(relationObj);
|
|
||||||
|
|
||||||
// Update PLTC indices incrementally (if initialized and not in batch mode)
|
// 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)
|
// For updates, only update if it's a new relation (not an existing one being modified)
|
||||||
|
|||||||
Reference in New Issue
Block a user