test: fix legacy fixture suites against modern DSL validation
CI / test (push) Successful in 25s
CI / publish (push) Has been skipped

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:
John Dvorak
2026-08-03 09:12:20 -07:00
parent ae21605fb7
commit 0c2ddc282b
6 changed files with 562 additions and 343 deletions
+90 -35
View File
@@ -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`);
});