@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.
Files changed (62) hide show
  1. package/LICENSE +180 -4
  2. package/README.md +2 -2
  3. package/dist/ClassFactory.d.ts.map +1 -1
  4. package/dist/ClassFactory.js +35 -6
  5. package/dist/ClassFactory.js.map +1 -1
  6. package/dist/SQLExpressionValidator.d.ts +28 -1
  7. package/dist/SQLExpressionValidator.d.ts.map +1 -1
  8. package/dist/SQLExpressionValidator.js +42 -4
  9. package/dist/SQLExpressionValidator.js.map +1 -1
  10. package/dist/SafeExpressionEvaluator.d.ts +131 -6
  11. package/dist/SafeExpressionEvaluator.d.ts.map +1 -1
  12. package/dist/SafeExpressionEvaluator.js +344 -41
  13. package/dist/SafeExpressionEvaluator.js.map +1 -1
  14. package/dist/__tests__/ClassFactory.test.js +40 -0
  15. package/dist/__tests__/ClassFactory.test.js.map +1 -1
  16. package/dist/__tests__/DeepDiff.test.d.ts +2 -0
  17. package/dist/__tests__/DeepDiff.test.d.ts.map +1 -0
  18. package/dist/__tests__/DeepDiff.test.js +229 -0
  19. package/dist/__tests__/DeepDiff.test.js.map +1 -0
  20. package/dist/__tests__/JSONValidator.test.d.ts +2 -0
  21. package/dist/__tests__/JSONValidator.test.d.ts.map +1 -0
  22. package/dist/__tests__/JSONValidator.test.js +221 -0
  23. package/dist/__tests__/JSONValidator.test.js.map +1 -0
  24. package/dist/__tests__/MJGlobal.test.js +8 -6
  25. package/dist/__tests__/MJGlobal.test.js.map +1 -1
  26. package/dist/__tests__/ResourcePatternUtils.test.d.ts +2 -0
  27. package/dist/__tests__/ResourcePatternUtils.test.d.ts.map +1 -0
  28. package/dist/__tests__/ResourcePatternUtils.test.js +50 -0
  29. package/dist/__tests__/ResourcePatternUtils.test.js.map +1 -0
  30. package/dist/__tests__/SQLExpressionValidator.security.test.js +62 -1
  31. package/dist/__tests__/SQLExpressionValidator.security.test.js.map +1 -1
  32. package/dist/__tests__/SafeExpressionEvaluator.security.test.d.ts +2 -0
  33. package/dist/__tests__/SafeExpressionEvaluator.security.test.d.ts.map +1 -0
  34. package/dist/__tests__/SafeExpressionEvaluator.security.test.js +284 -0
  35. package/dist/__tests__/SafeExpressionEvaluator.security.test.js.map +1 -0
  36. package/dist/__tests__/SafeExpressionEvaluator.test.d.ts +2 -0
  37. package/dist/__tests__/SafeExpressionEvaluator.test.d.ts.map +1 -0
  38. package/dist/__tests__/SafeExpressionEvaluator.test.js +355 -0
  39. package/dist/__tests__/SafeExpressionEvaluator.test.js.map +1 -0
  40. package/dist/__tests__/SafeExpressionEvaluator.validateSyntax.test.d.ts +2 -0
  41. package/dist/__tests__/SafeExpressionEvaluator.validateSyntax.test.d.ts.map +1 -0
  42. package/dist/__tests__/SafeExpressionEvaluator.validateSyntax.test.js +62 -0
  43. package/dist/__tests__/SafeExpressionEvaluator.validateSyntax.test.js.map +1 -0
  44. package/dist/__tests__/util.test.js +62 -1
  45. package/dist/__tests__/util.test.js.map +1 -1
  46. package/dist/__tests__/util.toEpochMs.test.d.ts +2 -0
  47. package/dist/__tests__/util.toEpochMs.test.d.ts.map +1 -0
  48. package/dist/__tests__/util.toEpochMs.test.js +57 -0
  49. package/dist/__tests__/util.toEpochMs.test.js.map +1 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +1 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/util/ResourcePatternUtils.d.ts +35 -0
  55. package/dist/util/ResourcePatternUtils.d.ts.map +1 -0
  56. package/dist/util/ResourcePatternUtils.js +56 -0
  57. package/dist/util/ResourcePatternUtils.js.map +1 -0
  58. package/dist/util.d.ts +40 -5
  59. package/dist/util.d.ts.map +1 -1
  60. package/dist/util.js +79 -25
  61. package/dist/util.js.map +1 -1
  62. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=SafeExpressionEvaluator.validateSyntax.test.d.ts.map
@@ -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('&lt;i&gt;x&lt;/i&gt;');
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