@fincity/kirun-js 2.16.1 → 2.16.2

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.
@@ -0,0 +1,282 @@
1
+ import { ExpressionEvaluator } from '../../../../src/engine/runtime/expression/ExpressionEvaluator';
2
+ import { TokenValueExtractor } from '../../../../src/engine/runtime/expression/tokenextractor/TokenValueExtractor';
3
+
4
+ class TestExtractor extends TokenValueExtractor {
5
+ private data: any;
6
+
7
+ constructor(data: any) {
8
+ super();
9
+ this.data = data;
10
+ }
11
+
12
+ protected getValueInternal(token: string): any {
13
+ const prefix = this.getPrefix();
14
+ const path = token.substring(prefix.length);
15
+ const parts = TokenValueExtractor['splitPath'](path);
16
+ return this.retrieveElementFrom(token, parts, 0, this.data);
17
+ }
18
+
19
+ public getPrefix(): string {
20
+ return 'Context.';
21
+ }
22
+
23
+ public getStore(): any {
24
+ return this.data;
25
+ }
26
+
27
+ // Expose retrieveElementFrom for testing
28
+ public retrieveElementFrom(
29
+ token: string,
30
+ parts: string[],
31
+ partNumber: number,
32
+ jsonElement: any,
33
+ ): any {
34
+ return super.retrieveElementFrom(token, parts, partNumber, jsonElement);
35
+ }
36
+ }
37
+
38
+ describe('ExpressionEvaluator with Bracket Notation', () => {
39
+ let testData: any;
40
+ let extractor: TestExtractor;
41
+ let evaluatorMap: Map<string, TokenValueExtractor>;
42
+
43
+ beforeEach(() => {
44
+ testData = {
45
+ obj: {
46
+ 'mail.props.port': 587,
47
+ 'mail.props.host': 'smtp.example.com',
48
+ 'api.key.secret': 'secret123',
49
+ simple: 'value',
50
+ count: 100,
51
+ },
52
+ arr: [10, 20, 30, 40, 50],
53
+ nested: {
54
+ 'field.with.dots': 'nestedValue',
55
+ regular: 'regularValue',
56
+ },
57
+ };
58
+ extractor = new TestExtractor(testData);
59
+ evaluatorMap = new Map([['Context.', extractor]]);
60
+ });
61
+
62
+ describe('Basic bracket notation with dotted keys', () => {
63
+ test('should access property with double quotes', () => {
64
+ const result = new ExpressionEvaluator('Context.obj["mail.props.port"]').evaluate(
65
+ evaluatorMap,
66
+ );
67
+ expect(result).toBe(587);
68
+ });
69
+
70
+ test('should access property with single quotes', () => {
71
+ const result = new ExpressionEvaluator("Context.obj['mail.props.host']").evaluate(
72
+ evaluatorMap,
73
+ );
74
+ expect(result).toBe('smtp.example.com');
75
+ });
76
+
77
+ test('should access nested property with dotted key', () => {
78
+ const result = new ExpressionEvaluator(
79
+ "Context.nested['field.with.dots']",
80
+ ).evaluate(evaluatorMap);
81
+ expect(result).toBe('nestedValue');
82
+ });
83
+ });
84
+
85
+ describe('Comparison operators with bracket notation', () => {
86
+ test('should work with equality operator (=)', () => {
87
+ const result = new ExpressionEvaluator(
88
+ 'Context.obj["mail.props.port"] = 587',
89
+ ).evaluate(evaluatorMap);
90
+ expect(result).toBe(true);
91
+ });
92
+
93
+ test('should work with not equal operator (!=)', () => {
94
+ const result = new ExpressionEvaluator(
95
+ 'Context.obj["mail.props.port"] != 500',
96
+ ).evaluate(evaluatorMap);
97
+ expect(result).toBe(true);
98
+ });
99
+
100
+ test('should work with greater than operator (>)', () => {
101
+ const result = new ExpressionEvaluator(
102
+ 'Context.obj["mail.props.port"] > 500',
103
+ ).evaluate(evaluatorMap);
104
+ expect(result).toBe(true);
105
+ });
106
+
107
+ test('should work with greater than or equal operator (>=)', () => {
108
+ const result = new ExpressionEvaluator(
109
+ 'Context.obj["mail.props.port"] >= 587',
110
+ ).evaluate(evaluatorMap);
111
+ expect(result).toBe(true);
112
+ });
113
+
114
+ test('should work with less than operator (<)', () => {
115
+ const result = new ExpressionEvaluator(
116
+ 'Context.obj["mail.props.port"] < 600',
117
+ ).evaluate(evaluatorMap);
118
+ expect(result).toBe(true);
119
+ });
120
+
121
+ test('should work with less than or equal operator (<=)', () => {
122
+ const result = new ExpressionEvaluator(
123
+ 'Context.obj["mail.props.port"] <= 587',
124
+ ).evaluate(evaluatorMap);
125
+ expect(result).toBe(true);
126
+ });
127
+ });
128
+
129
+ describe('Arithmetic operators with bracket notation', () => {
130
+ test('should work with addition', () => {
131
+ const result = new ExpressionEvaluator(
132
+ 'Context.obj["mail.props.port"] + 13',
133
+ ).evaluate(evaluatorMap);
134
+ expect(result).toBe(600);
135
+ });
136
+
137
+ test('should work with subtraction', () => {
138
+ const result = new ExpressionEvaluator(
139
+ 'Context.obj["mail.props.port"] - 87',
140
+ ).evaluate(evaluatorMap);
141
+ expect(result).toBe(500);
142
+ });
143
+
144
+ test('should work with multiplication', () => {
145
+ const result = new ExpressionEvaluator('Context.obj["count"] * 2').evaluate(
146
+ evaluatorMap,
147
+ );
148
+ expect(result).toBe(200);
149
+ });
150
+
151
+ test('should work with division', () => {
152
+ const result = new ExpressionEvaluator('Context.obj["count"] / 4').evaluate(
153
+ evaluatorMap,
154
+ );
155
+ expect(result).toBe(25);
156
+ });
157
+ });
158
+
159
+ describe('Ternary operator with bracket notation', () => {
160
+ test('should work with ternary operator', () => {
161
+ const result = new ExpressionEvaluator(
162
+ 'Context.obj["mail.props.port"] > 500 ? "high" : "low"',
163
+ ).evaluate(evaluatorMap);
164
+ expect(result).toBe('high');
165
+ });
166
+
167
+ test('should return false branch of ternary', () => {
168
+ const result = new ExpressionEvaluator(
169
+ 'Context.obj["mail.props.port"] < 500 ? "high" : "low"',
170
+ ).evaluate(evaluatorMap);
171
+ expect(result).toBe('low');
172
+ });
173
+ });
174
+
175
+ describe('Logical operators with bracket notation', () => {
176
+ test('should work with logical AND', () => {
177
+ const result = new ExpressionEvaluator(
178
+ 'Context.obj["mail.props.port"] > 500 and Context.obj["count"] = 100',
179
+ ).evaluate(evaluatorMap);
180
+ expect(result).toBe(true);
181
+ });
182
+
183
+ test('should work with logical OR', () => {
184
+ const result = new ExpressionEvaluator(
185
+ 'Context.obj["mail.props.port"] < 500 or Context.obj["count"] = 100',
186
+ ).evaluate(evaluatorMap);
187
+ expect(result).toBe(true);
188
+ });
189
+
190
+ test('should work with logical NOT', () => {
191
+ const result = new ExpressionEvaluator(
192
+ 'not Context.obj["mail.props.port"] < 500',
193
+ ).evaluate(evaluatorMap);
194
+ expect(result).toBe(true);
195
+ });
196
+ });
197
+
198
+ describe('Mixed bracket and dot notation', () => {
199
+ test('should work with bracket notation followed by dot notation', () => {
200
+ testData.obj['mail.props.port'] = { subfield: 'subvalue' };
201
+ const result = new ExpressionEvaluator(
202
+ 'Context.obj["mail.props.port"].subfield',
203
+ ).evaluate(evaluatorMap);
204
+ expect(result).toBe('subvalue');
205
+ });
206
+
207
+ test('should work with dot notation followed by bracket notation', () => {
208
+ const result = new ExpressionEvaluator(
209
+ "Context.nested['field.with.dots']",
210
+ ).evaluate(evaluatorMap);
211
+ expect(result).toBe('nestedValue');
212
+ });
213
+ });
214
+
215
+ describe('Array bracket notation (pre-existing functionality)', () => {
216
+ test('should work with array index access', () => {
217
+ const result = new ExpressionEvaluator('Context.arr[0]').evaluate(evaluatorMap);
218
+ expect(result).toBe(10);
219
+ });
220
+
221
+ test('should work with array index and comparison', () => {
222
+ const result = new ExpressionEvaluator('Context.arr[0] = 10').evaluate(
223
+ evaluatorMap,
224
+ );
225
+ expect(result).toBe(true);
226
+ });
227
+
228
+ test('should work with array index and arithmetic', () => {
229
+ const result = new ExpressionEvaluator('Context.arr[1] + Context.arr[2]').evaluate(
230
+ evaluatorMap,
231
+ );
232
+ expect(result).toBe(50);
233
+ });
234
+ });
235
+
236
+ describe('Complex expressions', () => {
237
+ test('should work with multiple bracket notations in one expression', () => {
238
+ const result = new ExpressionEvaluator(
239
+ 'Context.obj["mail.props.port"] + Context.obj["count"]',
240
+ ).evaluate(evaluatorMap);
241
+ expect(result).toBe(687);
242
+ });
243
+
244
+ test('should work with bracket notation in nested expression', () => {
245
+ const result = new ExpressionEvaluator(
246
+ '(Context.obj["mail.props.port"] > 500) and (Context.obj["count"] < 200)',
247
+ ).evaluate(evaluatorMap);
248
+ expect(result).toBe(true);
249
+ });
250
+
251
+ test('should work with string concatenation', () => {
252
+ const result = new ExpressionEvaluator(
253
+ 'Context.obj["mail.props.host"] + ":587"',
254
+ ).evaluate(evaluatorMap);
255
+ expect(result).toBe('smtp.example.com:587');
256
+ });
257
+ });
258
+
259
+ describe('Edge cases', () => {
260
+ test('should handle property with multiple dots', () => {
261
+ testData.obj['a.b.c.d'] = 'deepValue';
262
+ const result = new ExpressionEvaluator('Context.obj["a.b.c.d"]').evaluate(
263
+ evaluatorMap,
264
+ );
265
+ expect(result).toBe('deepValue');
266
+ });
267
+
268
+ test('should handle empty string key (though unusual)', () => {
269
+ testData.obj[''] = 'emptyKey';
270
+ const result = new ExpressionEvaluator('Context.obj[""]').evaluate(evaluatorMap);
271
+ expect(result).toBe('emptyKey');
272
+ });
273
+
274
+ test('should handle key with special characters', () => {
275
+ testData.obj['key@#$%'] = 'specialValue';
276
+ const result = new ExpressionEvaluator('Context.obj["key@#$%"]').evaluate(
277
+ evaluatorMap,
278
+ );
279
+ expect(result).toBe('specialValue');
280
+ });
281
+ });
282
+ });
@@ -37,3 +37,31 @@ test('Expression Test', () => {
37
37
  opInTheName = new Expression('Store.a.b.corStore.c.d.x');
38
38
  expect(opInTheName.toString()).toBe('(Store.(a.(b.(corStore.(c.(d.x))))))');
39
39
  });
40
+
41
+ test('Expression with bracket notation and quoted keys', () => {
42
+ // Test bracket notation with quoted keys containing dots
43
+ let expr = new Expression('Context.obj["mail.props.port"]');
44
+ expect(expr.toString()).toBe('(Context.(obj["mail.props.port"]))');
45
+
46
+ // Test with single quotes
47
+ expr = new Expression("Context.obj['api.key.secret']");
48
+ expect(expr.toString()).toBe("(Context.(obj['api.key.secret']))");
49
+
50
+ // Test with comparison operators
51
+ expr = new Expression('Context.obj["mail.props.port"] = 587');
52
+ expect(expr.toString()).toBe('((Context.(obj["mail.props.port"]))=587)');
53
+
54
+ expr = new Expression('Context.obj["mail.props.port"] != 500');
55
+ expect(expr.toString()).toBe('((Context.(obj["mail.props.port"]))!=500)');
56
+
57
+ expr = new Expression('Context.obj["mail.props.port"] > 500');
58
+ expect(expr.toString()).toBe('((Context.(obj["mail.props.port"]))>500)');
59
+
60
+ // Test with ternary operator
61
+ expr = new Expression('Context.obj["mail.props.port"] > 500 ? "high" : "low"');
62
+ expect(expr.toString()).toBe('(((Context.(obj["mail.props.port"]))>500)?"high":"low")');
63
+
64
+ // Test mix of bracket and dot notation
65
+ expr = new Expression('Context.obj["mail.props.port"].value');
66
+ expect(expr.toString()).toBe('(Context.(obj["mail.props.port"].value))');
67
+ });
@@ -74,3 +74,75 @@ test('TokenValueExtractor Test', () => {
74
74
  token = 'a.b.darr[2].length';
75
75
  expect(extractor.retrieveElementFrom(token, token.split(new RegExp('\\.')), 1, a)).toBe(4);
76
76
  });
77
+
78
+ test('Bracket notation with dotted keys', () => {
79
+ // Test bracket notation with keys containing dots
80
+ const config: any = {
81
+ 'mail.props.port': 587,
82
+ 'mail.props.host': 'smtp.example.com',
83
+ 'api.key.secret': 'secret123',
84
+ simple: 'value',
85
+ };
86
+
87
+ const obj: any = { config };
88
+
89
+ // Access splitPath via reflection since it's protected
90
+ const splitPath = (TokenValueExtractor as any).splitPath;
91
+
92
+ // Test with double quotes
93
+ let token = 'config["mail.props.port"]';
94
+ expect(extractor.retrieveElementFrom(token, splitPath(token), 0, obj)).toBe(587);
95
+
96
+ // Test with single quotes
97
+ token = "config['mail.props.host']";
98
+ expect(extractor.retrieveElementFrom(token, splitPath(token), 0, obj)).toBe('smtp.example.com');
99
+
100
+ // Test nested bracket notation with dots
101
+ token = "config['api.key.secret']";
102
+ expect(extractor.retrieveElementFrom(token, splitPath(token), 0, obj)).toBe('secret123');
103
+
104
+ // Test mix of dot and bracket notation
105
+ const nested = { 'field.with.dots': 'nestedValue' };
106
+ config.nested = nested;
107
+
108
+ token = "config.nested['field.with.dots']";
109
+ expect(extractor.retrieveElementFrom(token, splitPath(token), 0, obj)).toBe('nestedValue');
110
+
111
+ // Test that regular dot notation still works
112
+ token = 'config.simple';
113
+ expect(extractor.retrieveElementFrom(token, splitPath(token), 0, obj)).toBe('value');
114
+ });
115
+
116
+ test('splitPath correctly handles bracket notation', () => {
117
+ // Access splitPath via reflection since it's protected
118
+ const splitPath = (TokenValueExtractor as any).splitPath;
119
+
120
+ let parts: string[];
121
+
122
+ parts = splitPath("Context.obj['mail.props.port']");
123
+ expect(parts.length).toBe(2);
124
+ expect(parts[0]).toBe('Context');
125
+ expect(parts[1]).toBe("obj['mail.props.port']");
126
+
127
+ parts = splitPath("Context.obj['mail.props.port'].value");
128
+ expect(parts.length).toBe(3);
129
+ expect(parts[0]).toBe('Context');
130
+ expect(parts[1]).toBe("obj['mail.props.port']");
131
+ expect(parts[2]).toBe('value');
132
+
133
+ parts = splitPath("Steps.source.output['field.name']");
134
+ expect(parts.length).toBe(3);
135
+ expect(parts[0]).toBe('Steps');
136
+ expect(parts[1]).toBe('source');
137
+ expect(parts[2]).toBe("output['field.name']");
138
+
139
+ // Test that range operator (..) is preserved
140
+ parts = splitPath('array[0..5]');
141
+ expect(parts.length).toBe(1);
142
+ expect(parts[0]).toBe('array[0..5]');
143
+
144
+ // Test multiple bracket notations
145
+ parts = splitPath("obj['key.one']['key.two']");
146
+ expect(parts.length).toBe(1);
147
+ expect(parts[0]).toBe("obj['key.one']['key.two']");
148
+ });