@memberjunction/global 6.1.0-edge.2 → 6.1.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +180 -4
- package/README.md +2 -2
- package/dist/ClassFactory.d.ts.map +1 -1
- package/dist/ClassFactory.js +35 -6
- package/dist/ClassFactory.js.map +1 -1
- package/dist/SQLExpressionValidator.d.ts +28 -1
- package/dist/SQLExpressionValidator.d.ts.map +1 -1
- package/dist/SQLExpressionValidator.js +42 -4
- package/dist/SQLExpressionValidator.js.map +1 -1
- package/dist/SafeExpressionEvaluator.d.ts +131 -6
- package/dist/SafeExpressionEvaluator.d.ts.map +1 -1
- package/dist/SafeExpressionEvaluator.js +344 -41
- package/dist/SafeExpressionEvaluator.js.map +1 -1
- package/dist/__tests__/ClassFactory.test.js +40 -0
- package/dist/__tests__/ClassFactory.test.js.map +1 -1
- package/dist/__tests__/DeepDiff.test.d.ts +2 -0
- package/dist/__tests__/DeepDiff.test.d.ts.map +1 -0
- package/dist/__tests__/DeepDiff.test.js +229 -0
- package/dist/__tests__/DeepDiff.test.js.map +1 -0
- package/dist/__tests__/JSONValidator.test.d.ts +2 -0
- package/dist/__tests__/JSONValidator.test.d.ts.map +1 -0
- package/dist/__tests__/JSONValidator.test.js +221 -0
- package/dist/__tests__/JSONValidator.test.js.map +1 -0
- package/dist/__tests__/MJGlobal.test.js +8 -6
- package/dist/__tests__/MJGlobal.test.js.map +1 -1
- package/dist/__tests__/ResourcePatternUtils.test.d.ts +2 -0
- package/dist/__tests__/ResourcePatternUtils.test.d.ts.map +1 -0
- package/dist/__tests__/ResourcePatternUtils.test.js +50 -0
- package/dist/__tests__/ResourcePatternUtils.test.js.map +1 -0
- package/dist/__tests__/SQLExpressionValidator.security.test.js +62 -1
- package/dist/__tests__/SQLExpressionValidator.security.test.js.map +1 -1
- package/dist/__tests__/SafeExpressionEvaluator.security.test.d.ts +2 -0
- package/dist/__tests__/SafeExpressionEvaluator.security.test.d.ts.map +1 -0
- package/dist/__tests__/SafeExpressionEvaluator.security.test.js +284 -0
- package/dist/__tests__/SafeExpressionEvaluator.security.test.js.map +1 -0
- package/dist/__tests__/SafeExpressionEvaluator.test.d.ts +2 -0
- package/dist/__tests__/SafeExpressionEvaluator.test.d.ts.map +1 -0
- package/dist/__tests__/SafeExpressionEvaluator.test.js +355 -0
- package/dist/__tests__/SafeExpressionEvaluator.test.js.map +1 -0
- package/dist/__tests__/SafeExpressionEvaluator.validateSyntax.test.d.ts +2 -0
- package/dist/__tests__/SafeExpressionEvaluator.validateSyntax.test.d.ts.map +1 -0
- package/dist/__tests__/SafeExpressionEvaluator.validateSyntax.test.js +62 -0
- package/dist/__tests__/SafeExpressionEvaluator.validateSyntax.test.js.map +1 -0
- package/dist/__tests__/util.test.js +62 -1
- package/dist/__tests__/util.test.js.map +1 -1
- package/dist/__tests__/util.toEpochMs.test.d.ts +2 -0
- package/dist/__tests__/util.toEpochMs.test.d.ts.map +1 -0
- package/dist/__tests__/util.toEpochMs.test.js +57 -0
- package/dist/__tests__/util.toEpochMs.test.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/util/ResourcePatternUtils.d.ts +35 -0
- package/dist/util/ResourcePatternUtils.d.ts.map +1 -0
- package/dist/util/ResourcePatternUtils.js +56 -0
- package/dist/util/ResourcePatternUtils.js.map +1 -0
- package/dist/util.d.ts +40 -5
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +79 -25
- package/dist/util.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { SafeExpressionEvaluator, defaultExpressionEvaluator, } from '../SafeExpressionEvaluator.js';
|
|
3
|
+
/**
|
|
4
|
+
* Functional tests for SafeExpressionEvaluator.
|
|
5
|
+
*
|
|
6
|
+
* These exercise the documented contract: evaluate a boolean expression against a
|
|
7
|
+
* context object, returning an {@link ExpressionEvaluationResult}. Adversarial /
|
|
8
|
+
* sandbox-escape scenarios live in SafeExpressionEvaluator.security.test.ts.
|
|
9
|
+
*/
|
|
10
|
+
describe('SafeExpressionEvaluator', () => {
|
|
11
|
+
let evaluator;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
evaluator = new SafeExpressionEvaluator();
|
|
14
|
+
});
|
|
15
|
+
// ---------------------------------------------------------------
|
|
16
|
+
// Simple comparisons over context data
|
|
17
|
+
// ---------------------------------------------------------------
|
|
18
|
+
describe('comparison operators', () => {
|
|
19
|
+
it('should evaluate == to true when values match', () => {
|
|
20
|
+
const r = evaluator.evaluate("status == 'active'", { status: 'active' });
|
|
21
|
+
expect(r.success).toBe(true);
|
|
22
|
+
expect(r.value).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it('should evaluate == to false when values differ', () => {
|
|
25
|
+
const r = evaluator.evaluate("status == 'active'", { status: 'inactive' });
|
|
26
|
+
expect(r.success).toBe(true);
|
|
27
|
+
expect(r.value).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
it('should evaluate strict === correctly', () => {
|
|
30
|
+
expect(evaluator.evaluate('count === 5', { count: 5 }).value).toBe(true);
|
|
31
|
+
expect(evaluator.evaluate('count === 5', { count: 6 }).value).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it('should evaluate != and !==', () => {
|
|
34
|
+
expect(evaluator.evaluate('count != 5', { count: 6 }).value).toBe(true);
|
|
35
|
+
expect(evaluator.evaluate('count !== 5', { count: 5 }).value).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it('should evaluate <, >, <=, >=', () => {
|
|
38
|
+
expect(evaluator.evaluate('n < 10', { n: 5 }).value).toBe(true);
|
|
39
|
+
expect(evaluator.evaluate('n > 10', { n: 5 }).value).toBe(false);
|
|
40
|
+
expect(evaluator.evaluate('n <= 5', { n: 5 }).value).toBe(true);
|
|
41
|
+
expect(evaluator.evaluate('n >= 6', { n: 5 }).value).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
// ---------------------------------------------------------------
|
|
45
|
+
// Logical operators
|
|
46
|
+
// ---------------------------------------------------------------
|
|
47
|
+
describe('logical operators', () => {
|
|
48
|
+
it('should evaluate && (AND)', () => {
|
|
49
|
+
const ctx = { a: true, b: false };
|
|
50
|
+
expect(evaluator.evaluate('a && b', ctx).value).toBe(false);
|
|
51
|
+
expect(evaluator.evaluate('a && !b', ctx).value).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
it('should evaluate || (OR)', () => {
|
|
54
|
+
const ctx = { a: false, b: true };
|
|
55
|
+
expect(evaluator.evaluate('a || b', ctx).value).toBe(true);
|
|
56
|
+
expect(evaluator.evaluate('a || false', ctx).value).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
it('should evaluate ! (NOT)', () => {
|
|
59
|
+
expect(evaluator.evaluate('!isActive', { isActive: false }).value).toBe(true);
|
|
60
|
+
expect(evaluator.evaluate('!isActive', { isActive: true }).value).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
it('should combine comparisons with logical operators', () => {
|
|
63
|
+
const ctx = { tier: 'premium', total: 1500 };
|
|
64
|
+
const r = evaluator.evaluate("tier == 'premium' && total > 1000", ctx);
|
|
65
|
+
expect(r.success).toBe(true);
|
|
66
|
+
expect(r.value).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
// ---------------------------------------------------------------
|
|
70
|
+
// Nested property + array access
|
|
71
|
+
// ---------------------------------------------------------------
|
|
72
|
+
describe('property and array access', () => {
|
|
73
|
+
it('should resolve nested dot-notation properties', () => {
|
|
74
|
+
const ctx = {
|
|
75
|
+
payload: { customer: { tier: 'premium' }, order: { total: 1500 } },
|
|
76
|
+
};
|
|
77
|
+
const r = evaluator.evaluate("payload.customer.tier == 'premium' && payload.order.total > 1000", ctx);
|
|
78
|
+
expect(r.success).toBe(true);
|
|
79
|
+
expect(r.value).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
it('should resolve array index access', () => {
|
|
82
|
+
const ctx = { items: [10, 20, 30] };
|
|
83
|
+
expect(evaluator.evaluate('items[0] == 10', ctx).value).toBe(true);
|
|
84
|
+
expect(evaluator.evaluate('items[2] > items[0]', ctx).value).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
// ---------------------------------------------------------------
|
|
88
|
+
// Safe methods
|
|
89
|
+
// ---------------------------------------------------------------
|
|
90
|
+
describe('safe string/array methods', () => {
|
|
91
|
+
it('should support .length', () => {
|
|
92
|
+
expect(evaluator.evaluate('name.length > 3', { name: 'Alice' }).value).toBe(true);
|
|
93
|
+
expect(evaluator.evaluate('name.length > 3', { name: 'Al' }).value).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it('should support .includes()', () => {
|
|
96
|
+
expect(evaluator.evaluate("tags.includes('urgent')", { tags: ['work', 'urgent'] }).value).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
it('should support .startsWith() and .endsWith()', () => {
|
|
99
|
+
const ctx = { code: 'AB-123' };
|
|
100
|
+
expect(evaluator.evaluate("code.startsWith('AB')", ctx).value).toBe(true);
|
|
101
|
+
expect(evaluator.evaluate("code.endsWith('123')", ctx).value).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
it('should support .toLowerCase() in a comparison', () => {
|
|
104
|
+
const r = evaluator.evaluate("name.toLowerCase() == 'alice'", { name: 'ALICE' });
|
|
105
|
+
expect(r.success).toBe(true);
|
|
106
|
+
expect(r.value).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
// ---------------------------------------------------------------
|
|
110
|
+
// Array higher-order methods
|
|
111
|
+
// ---------------------------------------------------------------
|
|
112
|
+
describe('array higher-order methods', () => {
|
|
113
|
+
it('should support .some()', () => {
|
|
114
|
+
const ctx = { items: [{ price: 50 }, { price: 150 }] };
|
|
115
|
+
const r = evaluator.evaluate('items.some(item => item.price > 100)', ctx);
|
|
116
|
+
expect(r.success).toBe(true);
|
|
117
|
+
expect(r.value).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
it('should support .every()', () => {
|
|
120
|
+
const ctx = { items: [{ price: 50 }, { price: 150 }] };
|
|
121
|
+
expect(evaluator.evaluate('items.every(item => item.price > 0)', ctx).value).toBe(true);
|
|
122
|
+
expect(evaluator.evaluate('items.every(item => item.price > 100)', ctx).value).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
it('should support .filter().length', () => {
|
|
125
|
+
const ctx = {
|
|
126
|
+
items: [{ price: 50 }, { price: 150 }, { price: 200 }],
|
|
127
|
+
};
|
|
128
|
+
const r = evaluator.evaluate('items.filter(item => item.price > 100).length == 2', ctx);
|
|
129
|
+
expect(r.success).toBe(true);
|
|
130
|
+
expect(r.value).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
// ---------------------------------------------------------------
|
|
134
|
+
// typeof
|
|
135
|
+
// ---------------------------------------------------------------
|
|
136
|
+
describe('typeof', () => {
|
|
137
|
+
it('should evaluate typeof checks', () => {
|
|
138
|
+
expect(evaluator.evaluate("typeof name == 'string'", { name: 'x' }).value).toBe(true);
|
|
139
|
+
expect(evaluator.evaluate("typeof count == 'number'", { count: 5 }).value).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
// ---------------------------------------------------------------
|
|
143
|
+
// Edge values: null / undefined / empty
|
|
144
|
+
// ---------------------------------------------------------------
|
|
145
|
+
describe('edge values', () => {
|
|
146
|
+
it('should compare against null', () => {
|
|
147
|
+
expect(evaluator.evaluate('x == null', { x: null }).value).toBe(true);
|
|
148
|
+
expect(evaluator.evaluate('x === null', { x: null }).value).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
it('should treat missing property (undefined) loosely equal to null', () => {
|
|
151
|
+
const ctx = { obj: {} };
|
|
152
|
+
// obj.missing is undefined; undefined == null is true in JS
|
|
153
|
+
expect(evaluator.evaluate('obj.missing == null', ctx).value).toBe(true);
|
|
154
|
+
});
|
|
155
|
+
it('should coerce a truthy non-boolean result via Boolean()', () => {
|
|
156
|
+
// The evaluator wraps the expression in Boolean(...): a bare truthy string yields true
|
|
157
|
+
const r = evaluator.evaluate('name', { name: 'non-empty' });
|
|
158
|
+
expect(r.success).toBe(true);
|
|
159
|
+
expect(r.value).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
it('should coerce an empty-string result to false', () => {
|
|
162
|
+
const r = evaluator.evaluate('name', { name: '' });
|
|
163
|
+
expect(r.success).toBe(true);
|
|
164
|
+
expect(r.value).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
it('should handle an empty context object with a self-contained expression', () => {
|
|
167
|
+
const r = evaluator.evaluate('1 < 2', {});
|
|
168
|
+
expect(r.success).toBe(true);
|
|
169
|
+
expect(r.value).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
// ---------------------------------------------------------------
|
|
173
|
+
// Error paths for malformed / invalid input
|
|
174
|
+
// ---------------------------------------------------------------
|
|
175
|
+
describe('error handling', () => {
|
|
176
|
+
it('should fail on an empty expression', () => {
|
|
177
|
+
const r = evaluator.evaluate('', { a: 1 });
|
|
178
|
+
expect(r.success).toBe(false);
|
|
179
|
+
expect(r.error).toContain('non-empty string');
|
|
180
|
+
});
|
|
181
|
+
it('should evaluate a whitespace-only expression to false (Boolean() of no argument)', () => {
|
|
182
|
+
// A non-empty but whitespace-only string passes validation and compiles to
|
|
183
|
+
// `Boolean( )`, which is Boolean() === false — so it succeeds with value false.
|
|
184
|
+
const r = evaluator.evaluate(' ', { a: 1 });
|
|
185
|
+
expect(r.success).toBe(true);
|
|
186
|
+
expect(r.value).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
it('should fail on unbalanced parentheses', () => {
|
|
189
|
+
const r = evaluator.evaluate('(a > 1', { a: 5 });
|
|
190
|
+
expect(r.success).toBe(false);
|
|
191
|
+
expect(r.error).toContain('Unbalanced parentheses');
|
|
192
|
+
});
|
|
193
|
+
it('should fail on a closing paren before an opening paren', () => {
|
|
194
|
+
const r = evaluator.evaluate('a > 1)', { a: 5 });
|
|
195
|
+
expect(r.success).toBe(false);
|
|
196
|
+
expect(r.error).toContain('Unbalanced parentheses');
|
|
197
|
+
});
|
|
198
|
+
it('should fail on a syntactically malformed expression', () => {
|
|
199
|
+
const r = evaluator.evaluate('a ===', { a: 1 });
|
|
200
|
+
expect(r.success).toBe(false);
|
|
201
|
+
expect(r.error).toBeDefined();
|
|
202
|
+
});
|
|
203
|
+
it('should fail when referencing an unknown identifier not in context', () => {
|
|
204
|
+
const r = evaluator.evaluate('missingVar > 1', { a: 1 });
|
|
205
|
+
expect(r.success).toBe(false);
|
|
206
|
+
expect(r.error).toContain('Expression evaluation failed');
|
|
207
|
+
});
|
|
208
|
+
it('should fail when the expression exceeds the 1000-character limit', () => {
|
|
209
|
+
const long = '1 == 1 && '.repeat(200) + '1 == 1'; // well over 1000 chars
|
|
210
|
+
const r = evaluator.evaluate(long, {});
|
|
211
|
+
expect(r.success).toBe(false);
|
|
212
|
+
expect(r.error).toContain('maximum length');
|
|
213
|
+
});
|
|
214
|
+
it('should fail when a context key is not a valid JS identifier', () => {
|
|
215
|
+
// Context keys become Function parameter names; "my-key" is not a valid identifier.
|
|
216
|
+
const ctx = { 'my-key': 1 };
|
|
217
|
+
const r = evaluator.evaluate('1 == 1', ctx);
|
|
218
|
+
expect(r.success).toBe(false);
|
|
219
|
+
expect(r.error).toBeDefined();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
// ---------------------------------------------------------------
|
|
223
|
+
// Diagnostics
|
|
224
|
+
// ---------------------------------------------------------------
|
|
225
|
+
describe('diagnostics', () => {
|
|
226
|
+
it('should omit diagnostics by default', () => {
|
|
227
|
+
const r = evaluator.evaluate('a == 1', { a: 1 });
|
|
228
|
+
expect(r.diagnostics).toBeUndefined();
|
|
229
|
+
});
|
|
230
|
+
it('should include diagnostics when enabled on success', () => {
|
|
231
|
+
const r = evaluator.evaluate('a == 1', { a: 1 }, true);
|
|
232
|
+
expect(r.diagnostics).toBeDefined();
|
|
233
|
+
expect(r.diagnostics?.expression).toBe('a == 1');
|
|
234
|
+
expect(r.diagnostics?.context).toEqual({ a: 1 });
|
|
235
|
+
expect(typeof r.diagnostics?.evaluationTime).toBe('number');
|
|
236
|
+
});
|
|
237
|
+
it('should include diagnostics when enabled on failure', () => {
|
|
238
|
+
const r = evaluator.evaluate('', { a: 1 }, true);
|
|
239
|
+
expect(r.success).toBe(false);
|
|
240
|
+
expect(r.diagnostics).toBeDefined();
|
|
241
|
+
expect(r.diagnostics?.expression).toBe('');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
// ---------------------------------------------------------------
|
|
245
|
+
// Caching behaviour (observable: repeated evals stay consistent)
|
|
246
|
+
// ---------------------------------------------------------------
|
|
247
|
+
describe('compiled-expression cache', () => {
|
|
248
|
+
it('should return consistent results across repeated evaluations of the same expression', () => {
|
|
249
|
+
const first = evaluator.evaluate('a > 1', { a: 5 });
|
|
250
|
+
const second = evaluator.evaluate('a > 1', { a: 5 });
|
|
251
|
+
expect(first.value).toBe(true);
|
|
252
|
+
expect(second.value).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
it('should honour different context values on a cached expression', () => {
|
|
255
|
+
// Same expression + same context keys -> cache hit, but different values must still be applied
|
|
256
|
+
expect(evaluator.evaluate('a > 1', { a: 5 }).value).toBe(true);
|
|
257
|
+
expect(evaluator.evaluate('a > 1', { a: 0 }).value).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
// ---------------------------------------------------------------
|
|
261
|
+
// evaluateMultiple
|
|
262
|
+
// ---------------------------------------------------------------
|
|
263
|
+
describe('evaluateMultiple', () => {
|
|
264
|
+
it('should evaluate several named expressions against one context', () => {
|
|
265
|
+
const results = evaluator.evaluateMultiple([
|
|
266
|
+
{ expression: 'a > 1', name: 'aPositive' },
|
|
267
|
+
{ expression: "b == 'x'", name: 'bIsX' },
|
|
268
|
+
], { a: 5, b: 'x' });
|
|
269
|
+
expect(results.aPositive.value).toBe(true);
|
|
270
|
+
expect(results.bIsX.value).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
it('should key unnamed expressions by index', () => {
|
|
273
|
+
const results = evaluator.evaluateMultiple([{ expression: 'a > 1' }], { a: 5 });
|
|
274
|
+
expect(results.expression_0.value).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
it('should report per-expression failures independently', () => {
|
|
277
|
+
const results = evaluator.evaluateMultiple([
|
|
278
|
+
{ expression: 'a > 1', name: 'ok' },
|
|
279
|
+
{ expression: 'a ===', name: 'broken' },
|
|
280
|
+
], { a: 5 });
|
|
281
|
+
expect(results.ok.success).toBe(true);
|
|
282
|
+
expect(results.broken.success).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
// ---------------------------------------------------------------
|
|
286
|
+
// Safe globals — the shapes authored specs write
|
|
287
|
+
// ---------------------------------------------------------------
|
|
288
|
+
describe('safe global calls', () => {
|
|
289
|
+
// `condition-roots.ts` blesses these twelve names at the task-graph door, and
|
|
290
|
+
// `1efc248ac5` shipped that decision precisely because refusing them rejects specs
|
|
291
|
+
// that run correctly. Receiver and method are both fixed identifiers, so nothing
|
|
292
|
+
// here is reachable by string assembly.
|
|
293
|
+
const ctx = { payload: { count: 4, raw: '7', id: 42 }, output: { delta: -3 } };
|
|
294
|
+
it.each([
|
|
295
|
+
['Math.abs(output.delta) < 5', true],
|
|
296
|
+
['Math.max(payload.count, 9) === 9', true],
|
|
297
|
+
['Number(payload.count) > 3', true],
|
|
298
|
+
['String(payload.id).length > 0', true],
|
|
299
|
+
['Boolean(payload.count) === true', true],
|
|
300
|
+
['Array.isArray(output) === false', true],
|
|
301
|
+
['Object.keys(payload).length > 0', true],
|
|
302
|
+
["JSON.stringify(payload) !== ''", true],
|
|
303
|
+
['Date.now() > 0', true],
|
|
304
|
+
['parseInt(payload.raw) > 0', true],
|
|
305
|
+
['parseFloat(payload.raw) === 7', true],
|
|
306
|
+
['isNaN(payload.count) === false', true],
|
|
307
|
+
['isFinite(payload.count) === true', true],
|
|
308
|
+
['Number.isInteger(payload.count)', true],
|
|
309
|
+
])('should evaluate %s', (expression, expected) => {
|
|
310
|
+
const r = evaluator.evaluate(expression, ctx);
|
|
311
|
+
expect(r.error).toBeUndefined();
|
|
312
|
+
expect(r.value).toBe(expected);
|
|
313
|
+
});
|
|
314
|
+
it('should reject a namespace method that is not on the list', () => {
|
|
315
|
+
// Mutators stay out even on an allowed namespace.
|
|
316
|
+
const r = evaluator.evaluate('Object.assign(payload) !== null', ctx);
|
|
317
|
+
expect(r.success).toBe(false);
|
|
318
|
+
expect(r.error).toContain('forbidden construct');
|
|
319
|
+
});
|
|
320
|
+
it('should reject a global that is not on the list', () => {
|
|
321
|
+
expect(evaluator.evaluate('Symbol("x") !== null', {}).success).toBe(false);
|
|
322
|
+
expect(evaluator.evaluate('fetch("/x") !== null', {}).success).toBe(false);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
// ---------------------------------------------------------------
|
|
326
|
+
// Optional chaining
|
|
327
|
+
// ---------------------------------------------------------------
|
|
328
|
+
describe('optional chaining', () => {
|
|
329
|
+
it('should read through a present chain', () => {
|
|
330
|
+
const r = evaluator.evaluate("payload?.customer?.tier == 'premium'", {
|
|
331
|
+
payload: { customer: { tier: 'premium' } },
|
|
332
|
+
});
|
|
333
|
+
expect(r.value).toBe(true);
|
|
334
|
+
});
|
|
335
|
+
it('should short-circuit on an absent link rather than throwing', () => {
|
|
336
|
+
const r = evaluator.evaluate("payload?.customer?.tier == 'premium'", { payload: {} });
|
|
337
|
+
expect(r.success).toBe(true);
|
|
338
|
+
expect(r.value).toBe(false);
|
|
339
|
+
});
|
|
340
|
+
it('should apply the same rules to an optional index and an optional call', () => {
|
|
341
|
+
expect(evaluator.evaluate('items?.[0]?.price > 100', { items: [{ price: 150 }] }).value).toBe(true);
|
|
342
|
+
expect(evaluator.evaluate("name?.toLowerCase() == 'ada'", { name: 'Ada' }).value).toBe(true);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
// ---------------------------------------------------------------
|
|
346
|
+
// Default exported instance
|
|
347
|
+
// ---------------------------------------------------------------
|
|
348
|
+
describe('defaultExpressionEvaluator', () => {
|
|
349
|
+
it('should be a usable SafeExpressionEvaluator instance', () => {
|
|
350
|
+
expect(defaultExpressionEvaluator).toBeInstanceOf(SafeExpressionEvaluator);
|
|
351
|
+
expect(defaultExpressionEvaluator.evaluate('1 == 1', {}).value).toBe(true);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
//# sourceMappingURL=SafeExpressionEvaluator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SafeExpressionEvaluator.test.js","sourceRoot":"","sources":["../../src/__tests__/SafeExpressionEvaluator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SafeExpressionEvaluator.validateSyntax.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/SafeExpressionEvaluator.validateSyntax.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `validateSyntax` — checking that an expression *could* run, without running it.
|
|
3
|
+
*
|
|
4
|
+
* The distinction this draws is the whole point. `evaluate` reports two unrelated problems the same
|
|
5
|
+
* way: given an empty context, `payload.x > 1` fails with "payload is not defined" exactly as
|
|
6
|
+
* `payload.x >` fails with "Unexpected token". One is a typo; the other is a perfectly good
|
|
7
|
+
* condition that simply has no data yet. Anything built on `evaluate` to check authoring-time
|
|
8
|
+
* correctness therefore refuses every legitimate reference — which is the bug this method exists to
|
|
9
|
+
* make impossible.
|
|
10
|
+
*
|
|
11
|
+
* So the accept-list below matters more than the refuse-list: each entry is a shape somebody has
|
|
12
|
+
* already written, and over-refusing breaks saving work that runs fine.
|
|
13
|
+
*/
|
|
14
|
+
import { describe, it, expect } from 'vitest';
|
|
15
|
+
import { SafeExpressionEvaluator } from '../SafeExpressionEvaluator.js';
|
|
16
|
+
const evaluator = new SafeExpressionEvaluator();
|
|
17
|
+
describe('validateSyntax — parses without evaluating', () => {
|
|
18
|
+
it.each([
|
|
19
|
+
['payload.x > 1', 'an identifier that will only exist at runtime'],
|
|
20
|
+
['payload.x.y === \'a\'', 'a chain one property deeper than the envelope'],
|
|
21
|
+
['payload.title.includes(\'x\')', 'a method call on a value we cannot see yet'],
|
|
22
|
+
['stepResult.step === \'Success\'', 'the flow dialect'],
|
|
23
|
+
['unknownVar === 1', 'an identifier in no dialect at all — D2 may yet add it'],
|
|
24
|
+
['succeeded', 'the shortest legal condition there is'],
|
|
25
|
+
['output.count > 0 && !failed', 'operators either dialect uses'],
|
|
26
|
+
])('accepts %s (%s)', (expression) => {
|
|
27
|
+
expect(evaluator.validateSyntax(expression)).toEqual({ Valid: true });
|
|
28
|
+
});
|
|
29
|
+
it.each([
|
|
30
|
+
['payload.x >', 'a comparison with nothing on the right'],
|
|
31
|
+
['foo(', 'an unclosed call'],
|
|
32
|
+
['(payload.x > 1', 'unbalanced parentheses'],
|
|
33
|
+
['payload.x > 1)', 'a stray closer'],
|
|
34
|
+
['a ==== b', 'an operator that does not exist'],
|
|
35
|
+
])('refuses %s (%s)', (expression) => {
|
|
36
|
+
const verdict = evaluator.validateSyntax(expression);
|
|
37
|
+
expect(verdict.Valid).toBe(false);
|
|
38
|
+
expect(verdict.Error).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
it('refuses what the runtime would refuse anyway, rather than letting it hold forever', () => {
|
|
41
|
+
// These are policy, not grammar — but the evaluator rejects them at run time, so the edge
|
|
42
|
+
// would hold on every poll for the life of the graph. Better said now.
|
|
43
|
+
expect(evaluator.validateSyntax('a; b').Valid).toBe(false);
|
|
44
|
+
expect(evaluator.validateSyntax('this.x').Valid).toBe(false);
|
|
45
|
+
expect(evaluator.validateSyntax('x.constructor').Valid).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
it('never runs the expression it is checking', () => {
|
|
48
|
+
// The guarantee that makes compile-don't-execute safe. If validation invoked the body, a
|
|
49
|
+
// condition would have side effects at SAVE time — on somebody else's machine.
|
|
50
|
+
let touched = false;
|
|
51
|
+
const globalScope = globalThis;
|
|
52
|
+
globalScope.__mjConditionProbe = () => { touched = true; return true; };
|
|
53
|
+
try {
|
|
54
|
+
evaluator.validateSyntax('__mjConditionProbe()');
|
|
55
|
+
expect(touched).toBe(false);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
delete globalScope.__mjConditionProbe;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=SafeExpressionEvaluator.validateSyntax.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SafeExpressionEvaluator.validateSyntax.test.js","sourceRoot":"","sources":["../../src/__tests__/SafeExpressionEvaluator.validateSyntax.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAErE,MAAM,SAAS,GAAG,IAAI,uBAAuB,EAAE,CAAC;AAEhD,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IACxD,EAAE,CAAC,IAAI,CAAC;QACJ,CAAC,eAAe,EAAE,+CAA+C,CAAC;QAClE,CAAC,uBAAuB,EAAE,+CAA+C,CAAC;QAC1E,CAAC,+BAA+B,EAAE,4CAA4C,CAAC;QAC/E,CAAC,iCAAiC,EAAE,kBAAkB,CAAC;QACvD,CAAC,kBAAkB,EAAE,wDAAwD,CAAC;QAC9E,CAAC,WAAW,EAAE,uCAAuC,CAAC;QACtD,CAAC,6BAA6B,EAAE,+BAA+B,CAAC;KACnE,CAAC,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,EAAE;QACjC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACJ,CAAC,aAAa,EAAE,wCAAwC,CAAC;QACzD,CAAC,MAAM,EAAE,kBAAkB,CAAC;QAC5B,CAAC,gBAAgB,EAAE,wBAAwB,CAAC;QAC5C,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;QACpC,CAAC,UAAU,EAAE,iCAAiC,CAAC;KAClD,CAAC,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,EAAE;QACjC,MAAM,OAAO,GAAG,SAAS,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QACrD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,GAAG,EAAE;QACzF,0FAA0F;QAC1F,uEAAuE;QACvE,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3D,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7D,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAChD,yFAAyF;QACzF,+EAA+E;QAC/E,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,WAAW,GAAG,UAAgD,CAAC;QACrE,WAAW,CAAC,kBAAkB,GAAG,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;QACxE,IAAI,CAAC;YACD,SAAS,CAAC,cAAc,CAAC,sBAAsB,CAAC,CAAC;YACjD,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;gBAAS,CAAC;YACP,OAAO,WAAW,CAAC,kBAAkB,CAAC;QAC1C,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { GetGlobalObjectStore, CleanJSON, SafeJSONParse, CleanAndParseJSON, CopyScalarsAndArrays, convertCamelCaseToHaveSpaces, createDisplayName, generatePluralName, getIrregularPlural, stripWhitespace, uuidv4, stripTrailingChars, replaceAllSpaces, IsOnlyTimezoneShift, EscapeHTML, HighlightSearchMatches, } from '../util.js';
|
|
2
|
+
import { GetGlobalObjectStore, CleanJSON, SafeJSONParse, CleanAndParseJSON, CopyScalarsAndArrays, convertCamelCaseToHaveSpaces, createDisplayName, generatePluralName, getIrregularPlural, stripWhitespace, uuidv4, stripTrailingChars, replaceAllSpaces, IsOnlyTimezoneShift, EscapeHTML, EscapeSQLString, HighlightSearchMatches, } from '../util.js';
|
|
3
3
|
describe('GetGlobalObjectStore', () => {
|
|
4
4
|
it('should return a non-null object in Node environment', () => {
|
|
5
5
|
const store = GetGlobalObjectStore();
|
|
@@ -18,6 +18,49 @@ describe('GetGlobalObjectStore', () => {
|
|
|
18
18
|
delete store[testKey];
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
|
+
// The three cases above predate the memoised `typeof` probe and pass either way: they assert
|
|
22
|
+
// the result is non-null, defined, and writable. The ones below pin what actually changed.
|
|
23
|
+
it('resolves to Node\'s global object, not merely something non-null', () => {
|
|
24
|
+
expect(GetGlobalObjectStore()).toBe(global);
|
|
25
|
+
});
|
|
26
|
+
it('is memoised — every call returns the identical reference', () => {
|
|
27
|
+
const first = GetGlobalObjectStore();
|
|
28
|
+
const second = GetGlobalObjectStore();
|
|
29
|
+
expect(second).toBe(first);
|
|
30
|
+
expect(GetGlobalObjectStore()).toBe(first);
|
|
31
|
+
});
|
|
32
|
+
it('answers from the memo after the environment changes, by design', async () => {
|
|
33
|
+
// The environment is fixed at startup, which is what makes memoising sound. Stated as a test
|
|
34
|
+
// because it is the one surprise the memo introduces: a caller that installs a `window`
|
|
35
|
+
// mid-process still gets the answer computed on first call. If this ever needs to change,
|
|
36
|
+
// it is a deliberate decision, not an accident.
|
|
37
|
+
const before = GetGlobalObjectStore();
|
|
38
|
+
const g = globalThis;
|
|
39
|
+
g.window = { installed: 'later' };
|
|
40
|
+
try {
|
|
41
|
+
expect(GetGlobalObjectStore()).toBe(before);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
delete g.window;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it('treats a declared-but-null window as absent and falls through to global', async () => {
|
|
48
|
+
// `typeof null === 'object'`, so a bare `typeof window !== 'undefined'` probe would accept a
|
|
49
|
+
// null window and memoise it as the store — permanently, for the life of the process. SSR
|
|
50
|
+
// shims that null out `window` exist in the wild, and MJ is embedded by callers whose
|
|
51
|
+
// environment we do not control. Needs a fresh module so the memo is unset.
|
|
52
|
+
const g = globalThis;
|
|
53
|
+
g.window = null;
|
|
54
|
+
vi.resetModules();
|
|
55
|
+
try {
|
|
56
|
+
const fresh = await import('../util.js');
|
|
57
|
+
expect(fresh.GetGlobalObjectStore()).toBe(global);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
delete g.window;
|
|
61
|
+
vi.resetModules();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
21
64
|
});
|
|
22
65
|
describe('CleanJSON', () => {
|
|
23
66
|
it('should return null for null input', () => {
|
|
@@ -561,4 +604,22 @@ describe('HighlightSearchMatches', () => {
|
|
|
561
604
|
expect(HighlightSearchMatches('<i>x</i>', ' ')).toBe('<i>x</i>');
|
|
562
605
|
});
|
|
563
606
|
});
|
|
607
|
+
describe('EscapeSQLString', () => {
|
|
608
|
+
it('escapes single quotes by doubling them', () => {
|
|
609
|
+
expect(EscapeSQLString("O'Brien")).toBe("O''Brien");
|
|
610
|
+
expect(EscapeSQLString("user's 'name'")).toBe("user''s ''name''");
|
|
611
|
+
});
|
|
612
|
+
it('strips null bytes to prevent injection', () => {
|
|
613
|
+
expect(EscapeSQLString("test\0injection")).toBe("testinjection");
|
|
614
|
+
expect(EscapeSQLString("user\0' OR '1'='1")).toBe("user'' OR ''1''=''1");
|
|
615
|
+
});
|
|
616
|
+
it('handles null and undefined safely', () => {
|
|
617
|
+
expect(EscapeSQLString(null)).toBe('');
|
|
618
|
+
expect(EscapeSQLString(undefined)).toBe('');
|
|
619
|
+
});
|
|
620
|
+
it('handles empty string and clean strings', () => {
|
|
621
|
+
expect(EscapeSQLString('')).toBe('');
|
|
622
|
+
expect(EscapeSQLString('plain.email@example.com')).toBe('plain.email@example.com');
|
|
623
|
+
});
|
|
624
|
+
});
|
|
564
625
|
//# sourceMappingURL=util.test.js.map
|