test: fix legacy fixture suites against modern DSL validation
The legacy test files (Definition/Evidence/Expression/Fact/Integration/ Measure) predate the DSL's validation rules: reserved built-in types (User/Account/Device/AuthSession) and the boolean/predicate-call requirements on evidence statements. Fixed the fixtures, not the engine: - reserved renames: User->Employee, Account->Tenant (Device/AuthSession were only legally referenced) - expression fixtures: comparators now use predicate calls on both sides (fact score(value: number) + score(1 + 2 * 3) > score(0)); measure bodies carry the original arbitrary expressions (validated, not rule-generated) - grammar-shape fixes: comma-separated fusion/aggregate lists, PROVIDES X (no array returns), predicate-call evidence statements All 54 tests across the 8 files pass.
This commit is contained in:
+90
-35
@@ -12,6 +12,57 @@ function createMockArbiter() {
|
||||
};
|
||||
}
|
||||
|
||||
// The compiler validates evidence bodies as generated rules, which only accept
|
||||
// predicate-call forms. Pure expression forms (booleans, arithmetic, within,
|
||||
// attribute access, && / || chains) still parse and validate inside measure
|
||||
// bodies, which are checked but not rule-generated. So expression-precedence
|
||||
// fixtures use measures, while comparator fixtures use predicate calls on both
|
||||
// sides of the operator.
|
||||
const SCORE_FACT = `
|
||||
fact score(value: number)
|
||||
`;
|
||||
|
||||
const EMPLOYEE_FIELDS = `
|
||||
definition Employee {
|
||||
role: string
|
||||
isActive: boolean
|
||||
isSuspended: boolean
|
||||
isBlacklisted: boolean
|
||||
isTrusted: boolean
|
||||
lastActive: timestamp
|
||||
lastLogin: timestamp
|
||||
createdAt: timestamp
|
||||
lastActivity: timestamp
|
||||
hasEmergencyAccess: boolean
|
||||
balance: number
|
||||
score: number
|
||||
profile: Profile
|
||||
permissions: Permission[]
|
||||
}
|
||||
|
||||
definition Profile {
|
||||
name: string
|
||||
permissions: Permission[]
|
||||
}
|
||||
|
||||
definition Permission {
|
||||
name: string
|
||||
}
|
||||
|
||||
definition Resource {
|
||||
name: string
|
||||
}
|
||||
|
||||
definition Document {
|
||||
name: string
|
||||
}
|
||||
|
||||
fact hasRole(user: any, role: string)
|
||||
fact isMember(user: any, group: any)
|
||||
fact hasPermission(user: any, resource: any, action: string)
|
||||
fact isActive(user: any)
|
||||
`;
|
||||
|
||||
describe('Expression Parsing', () => {
|
||||
const arbiter = createMockArbiter();
|
||||
const compiler = new DSLCompiler(arbiter);
|
||||
@@ -19,34 +70,34 @@ describe('Expression Parsing', () => {
|
||||
test('Arithmetic operator precedence', () => {
|
||||
const testCases = [
|
||||
{
|
||||
input: '1 + 2 * 3',
|
||||
input: 'score(1 + 2 * 3) > score(0)',
|
||||
expected: 'Should evaluate as 1 + (2 * 3) = 7',
|
||||
description: 'Multiplication before addition'
|
||||
},
|
||||
{
|
||||
input: '10 - 3 * 2',
|
||||
input: 'score(10 - 3 * 2) > score(0)',
|
||||
expected: 'Should evaluate as 10 - (3 * 2) = 4',
|
||||
description: 'Multiplication before subtraction'
|
||||
},
|
||||
{
|
||||
input: '8 / 2 * 4',
|
||||
input: 'score(8 / 2 * 4) > score(0)',
|
||||
expected: 'Should evaluate as (8 / 2) * 4 = 16',
|
||||
description: 'Left-associative division and multiplication'
|
||||
},
|
||||
{
|
||||
input: '2 + 3 * 4 - 5',
|
||||
input: 'score(2 + 3 * 4 - 5) > score(0)',
|
||||
expected: 'Should evaluate as 2 + (3 * 4) - 5 = 9',
|
||||
description: 'Mixed arithmetic with correct precedence'
|
||||
},
|
||||
{
|
||||
input: '(1 + 2) * 3',
|
||||
input: 'score((1 + 2) * 3) > score(0)',
|
||||
expected: 'Should evaluate as (1 + 2) * 3 = 9',
|
||||
description: 'Parentheses override precedence'
|
||||
}
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, expected, description }) => {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const dsl = SCORE_FACT + `evidence test() { ${input} }`;
|
||||
const result = compiler.compile(dsl, `test-arithmetic-${Date.now()}`);
|
||||
assert.ok(result.success, `${description} should parse successfully`);
|
||||
});
|
||||
@@ -82,7 +133,7 @@ describe('Expression Parsing', () => {
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, expected, description }) => {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const dsl = `measure test() { ${input} } PROVIDES boolean`;
|
||||
const result = compiler.compile(dsl, `test-logical-${Date.now()}`);
|
||||
assert.ok(result.success, `${description} should parse successfully`);
|
||||
});
|
||||
@@ -90,18 +141,19 @@ describe('Expression Parsing', () => {
|
||||
|
||||
test('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' }
|
||||
{ input: 'score(1) == score(1)', description: 'Equality comparison', measure: false },
|
||||
{ input: 'score(1) != score(2)', description: 'Inequality comparison', measure: false },
|
||||
{ input: 'score(5) > score(3)', description: 'Greater than', measure: false },
|
||||
{ input: 'score(3) < score(5)', description: 'Less than', measure: false },
|
||||
{ input: 'score(4) >= score(4)', description: 'Greater than or equal', measure: false },
|
||||
{ input: 'score(4) <= score(4)', description: 'Less than or equal', measure: false },
|
||||
{ input: 'score(1) == score(1) && score(2) > score(1)', description: 'Comparison with logical operators', measure: true },
|
||||
{ input: 'score(1 + 2) == score(3)', description: 'Arithmetic in comparison', measure: false }
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
testCases.forEach(({ input, description, measure }) => {
|
||||
const body = `test() { ${input} }`;
|
||||
const dsl = SCORE_FACT + (measure ? `measure ${body} PROVIDES boolean` : `evidence ${body}`);
|
||||
const result = compiler.compile(dsl, `test-comparison-${Date.now()}`);
|
||||
assert.ok(result.success, `${description} should parse successfully`);
|
||||
});
|
||||
@@ -116,7 +168,7 @@ describe('Expression Parsing', () => {
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const dsl = EMPLOYEE_FIELDS + `measure test(user: Employee) { ${input} } PROVIDES boolean`;
|
||||
const result = compiler.compile(dsl, `test-temporal-${Date.now()}`);
|
||||
assert.ok(result.success, `${description} should parse successfully`);
|
||||
});
|
||||
@@ -124,14 +176,14 @@ describe('Expression Parsing', () => {
|
||||
|
||||
test('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' }
|
||||
{ input: 'NOT true', description: 'NOT operator', params: '' },
|
||||
{ input: '! false', description: 'Alternative NOT operator', params: '' },
|
||||
{ input: 'NOT (true && false)', description: 'NOT with parenthesized expression', params: '' },
|
||||
{ input: 'NOT user.isSuspended', description: 'NOT with attribute access', params: 'user: Employee' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
testCases.forEach(({ input, description, params }) => {
|
||||
const dsl = EMPLOYEE_FIELDS + `measure test(${params}) { ${input} } PROVIDES boolean`;
|
||||
const result = compiler.compile(dsl, `test-unary-${Date.now()}`);
|
||||
assert.ok(result.success, `${description} should parse successfully`);
|
||||
});
|
||||
@@ -141,13 +193,13 @@ describe('Expression Parsing', () => {
|
||||
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.permissions', description: 'Array access' },
|
||||
{ input: 'user.profile.permissions', description: 'Nested attribute with array access' },
|
||||
{ input: 'user.isActive && user.role == "admin"', description: 'Attribute access in logical expression' }
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const dsl = EMPLOYEE_FIELDS + `measure test(user: Employee) { ${input} } PROVIDES boolean`;
|
||||
const result = compiler.compile(dsl, `test-attribute-${Date.now()}`);
|
||||
assert.ok(result.success, `${description} should parse successfully`);
|
||||
});
|
||||
@@ -155,15 +207,17 @@ describe('Expression Parsing', () => {
|
||||
|
||||
test('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' }
|
||||
{ input: 'hasRole(user, "admin")', description: 'Simple function call', measure: false },
|
||||
{ input: 'isMember(user, group)', description: 'Function call with variables', measure: false },
|
||||
{ input: 'hasPermission(user, resource, "read")', description: 'Function call with multiple arguments', measure: false },
|
||||
{ input: 'hasRole(user, "admin") && isActive(user)', description: 'Multiple function calls', measure: true },
|
||||
{ input: 'hasRole(user, user.role)', description: 'Function call with attribute access', measure: false }
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
testCases.forEach(({ input, description, measure }) => {
|
||||
const params = 'user: Employee, group: Employee, resource: Resource';
|
||||
const body = `test(${params}) { ${input} }`;
|
||||
const dsl = EMPLOYEE_FIELDS + (measure ? `measure ${body} PROVIDES boolean` : `evidence ${body}`);
|
||||
const result = compiler.compile(dsl, `test-function-${Date.now()}`);
|
||||
assert.ok(result.success, `${description} should parse successfully`);
|
||||
});
|
||||
@@ -194,7 +248,8 @@ describe('Expression Parsing', () => {
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, description }) => {
|
||||
const dsl = `evidence test() { ${input} }`;
|
||||
const params = 'user: Employee, resource: Resource, doc: Document';
|
||||
const dsl = EMPLOYEE_FIELDS + `measure test(${params}) { ${input} } PROVIDES boolean`;
|
||||
const result = compiler.compile(dsl, `test-complex-${Date.now()}`);
|
||||
assert.ok(result.success, `${description} should parse successfully`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user