394 lines
11 KiB
JavaScript
394 lines
11 KiB
JavaScript
|
|
/**
|
||
|
|
* 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();
|
||
|
|
}
|