port-ocean 0.28.11__py3-none-any.whl → 0.28.14__py3-none-any.whl

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.

Potentially problematic release.


This version of port-ocean might be problematic. Click here for more details.

@@ -0,0 +1,932 @@
1
+ from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
2
+ InputClassifyingResult,
3
+ can_expression_run_with_no_input,
4
+ _can_expression_run_on_single_item,
5
+ classify_input,
6
+ _mask_strings,
7
+ _mask_numbers,
8
+ )
9
+
10
+
11
+ class TestMaskStrings:
12
+ """Test the _mask_strings function"""
13
+
14
+ def test_mask_simple_string(self) -> None:
15
+ """Test masking a simple string literal"""
16
+ expr = '"hello world"'
17
+ result = _mask_strings(expr)
18
+ assert result == "S"
19
+
20
+ def test_mask_string_with_escaped_quotes(self) -> None:
21
+ """Test masking a string with escaped quotes"""
22
+ expr = '"hello \\"world\\""'
23
+ result = _mask_strings(expr)
24
+ assert result == "S"
25
+
26
+ def test_mask_string_with_backslash(self) -> None:
27
+ """Test masking a string with backslash"""
28
+ expr = '"hello\\\\world"'
29
+ result = _mask_strings(expr)
30
+ assert result == "S"
31
+
32
+ def test_mask_multiple_strings(self) -> None:
33
+ """Test masking multiple string literals"""
34
+ expr = '"hello" + "world"'
35
+ result = _mask_strings(expr)
36
+ assert result == "S + S"
37
+
38
+ def test_mask_string_with_dots_inside(self) -> None:
39
+ """Test masking a string that contains dots (should not affect dot detection)"""
40
+ expr = '"this.is.a.string" + .field'
41
+ result = _mask_strings(expr)
42
+ assert result == "S + .field"
43
+
44
+ def test_mask_empty_string(self) -> None:
45
+ """Test masking an empty string"""
46
+ expr = '""'
47
+ result = _mask_strings(expr)
48
+ assert result == "S"
49
+
50
+ def test_no_strings_to_mask(self) -> None:
51
+ """Test expression with no strings"""
52
+ expr = ".field + .other"
53
+ result = _mask_strings(expr)
54
+ assert result == ".field + .other"
55
+
56
+ def test_mixed_content(self) -> None:
57
+ """Test masking with mixed content"""
58
+ expr = '"hello" + .field + "world"'
59
+ result = _mask_strings(expr)
60
+ assert result == "S + .field + S"
61
+
62
+
63
+ class TestMaskNumbers:
64
+ """Test the _mask_numbers function"""
65
+
66
+ def test_mask_simple_number(self) -> None:
67
+ """Test masking a simple number"""
68
+ expr = "42"
69
+ result = _mask_numbers(expr)
70
+ assert result == "N"
71
+
72
+ def test_mask_decimal_number(self) -> None:
73
+ """Test masking a decimal number"""
74
+ expr = "3.14"
75
+ result = _mask_numbers(expr)
76
+ assert result == "N"
77
+
78
+ def test_mask_negative_number(self) -> None:
79
+ """Test masking a negative number"""
80
+ expr = "-42"
81
+ result = _mask_numbers(expr)
82
+ assert result == "N"
83
+
84
+ def test_mask_negative_decimal(self) -> None:
85
+ """Test masking a negative decimal"""
86
+ expr = "-3.14"
87
+ result = _mask_numbers(expr)
88
+ assert result == "N"
89
+
90
+ def test_mask_positive_number_with_sign(self) -> None:
91
+ """Test masking a positive number with explicit sign"""
92
+ expr = "+42"
93
+ result = _mask_numbers(expr)
94
+ assert result == "N"
95
+
96
+ def test_mask_multiple_numbers(self) -> None:
97
+ """Test masking multiple numbers"""
98
+ expr = "3.14 + 2.5"
99
+ result = _mask_numbers(expr)
100
+ assert result == "N + N"
101
+
102
+ def test_mask_numbers_with_operators(self) -> None:
103
+ """Test masking numbers with various operators"""
104
+ expr = "3.14 * 2.5 - 1.5 / 2"
105
+ result = _mask_numbers(expr)
106
+ assert result == "N * N - N / N"
107
+
108
+ def test_mask_numbers_with_strings(self) -> None:
109
+ """Test masking numbers mixed with strings (should not affect strings)"""
110
+ expr = '"hello" + 3.14 + "world"'
111
+ result = _mask_numbers(expr)
112
+ assert result == '"hello" + N + "world"'
113
+
114
+ def test_mask_numbers_with_field_references(self) -> None:
115
+ """Test masking numbers with field references (should not affect field references)"""
116
+ expr = "3.14 + .field"
117
+ result = _mask_numbers(expr)
118
+ assert result == "N + .field"
119
+
120
+ def test_no_numbers_to_mask(self) -> None:
121
+ """Test expression with no numbers"""
122
+ expr = ".field + .other"
123
+ result = _mask_numbers(expr)
124
+ assert result == ".field + .other"
125
+
126
+ def test_mixed_content_with_numbers(self) -> None:
127
+ """Test masking with mixed content including numbers"""
128
+ expr = '"hello" + 3.14 + .field + 42'
129
+ result = _mask_numbers(expr)
130
+ assert result == '"hello" + N + .field + N'
131
+
132
+
133
+ class TestCanExpressionRunWithNoInput:
134
+ """Test the can_expression_run_with_no_input function"""
135
+
136
+ def test_empty_string(self) -> None:
137
+ """Test empty string returns True"""
138
+ assert can_expression_run_with_no_input("") is True
139
+ assert can_expression_run_with_no_input(" ") is True
140
+
141
+ def test_pure_string_literal(self) -> None:
142
+ """Test pure string literals return True"""
143
+ assert can_expression_run_with_no_input('"hello"') is True
144
+ assert can_expression_run_with_no_input('"hello world"') is True
145
+ assert can_expression_run_with_no_input('"hello\\"world"') is True
146
+ assert can_expression_run_with_no_input('"this.is.a.string"') is True
147
+
148
+ def test_string_with_dots_inside(self) -> None:
149
+ """Test string literals with dots inside return True"""
150
+ assert can_expression_run_with_no_input('"this.is.a.string"') is True
151
+ assert can_expression_run_with_no_input('"path/to/file"') is True
152
+
153
+ def test_contains_dots_outside_strings(self) -> None:
154
+ """Test expressions with dots outside strings return False"""
155
+ assert can_expression_run_with_no_input(".field") is False
156
+ assert can_expression_run_with_no_input('"hello" + .field') is False
157
+ assert can_expression_run_with_no_input('.field + "world"') is False
158
+
159
+ def test_input_dependent_functions(self) -> None:
160
+ """Test expressions with input-dependent functions return False"""
161
+ assert can_expression_run_with_no_input("map(.field)") is False
162
+ assert can_expression_run_with_no_input("select(.field)") is False
163
+ assert can_expression_run_with_no_input("length(.field)") is False
164
+ assert can_expression_run_with_no_input("keys(.field)") is False
165
+ assert can_expression_run_with_no_input("values(.field)") is False
166
+ assert can_expression_run_with_no_input("has(.field)") is False
167
+ assert can_expression_run_with_no_input("in(.field)") is False
168
+ assert can_expression_run_with_no_input("index(.field)") is False
169
+ assert can_expression_run_with_no_input("indices(.field)") is False
170
+ assert can_expression_run_with_no_input("contains(.field)") is False
171
+ assert can_expression_run_with_no_input("paths(.field)") is False
172
+ assert can_expression_run_with_no_input("leaf_paths(.field)") is False
173
+ assert can_expression_run_with_no_input("to_entries(.field)") is False
174
+ assert can_expression_run_with_no_input("from_entries(.field)") is False
175
+ assert can_expression_run_with_no_input("with_entries(.field)") is False
176
+ assert can_expression_run_with_no_input("del(.field)") is False
177
+ assert can_expression_run_with_no_input("delpaths(.field)") is False
178
+ assert can_expression_run_with_no_input("walk(.field)") is False
179
+ assert can_expression_run_with_no_input("reduce(.field)") is False
180
+ assert can_expression_run_with_no_input("foreach(.field)") is False
181
+ assert can_expression_run_with_no_input("input(.field)") is False
182
+ assert can_expression_run_with_no_input("inputs(.field)") is False
183
+ assert can_expression_run_with_no_input("limit(.field)") is False
184
+ assert can_expression_run_with_no_input("first(.field)") is False
185
+ assert can_expression_run_with_no_input("last(.field)") is False
186
+ assert can_expression_run_with_no_input("nth(.field)") is False
187
+ assert can_expression_run_with_no_input("while(.field)") is False
188
+ assert can_expression_run_with_no_input("until(.field)") is False
189
+ assert can_expression_run_with_no_input("recurse(.field)") is False
190
+ assert can_expression_run_with_no_input("recurse_down(.field)") is False
191
+ assert can_expression_run_with_no_input("bsearch(.field)") is False
192
+ assert can_expression_run_with_no_input("combinations(.field)") is False
193
+ assert can_expression_run_with_no_input("permutations(.field)") is False
194
+
195
+ def test_nullary_expressions(self) -> None:
196
+ """Test nullary expressions return True"""
197
+ assert can_expression_run_with_no_input("null") is True
198
+ assert can_expression_run_with_no_input("true") is True
199
+ assert can_expression_run_with_no_input("false") is True
200
+ assert can_expression_run_with_no_input("42") is True
201
+ assert can_expression_run_with_no_input("-42") is True
202
+ assert can_expression_run_with_no_input("3.14") is True
203
+ assert can_expression_run_with_no_input("-3.14") is True
204
+ assert can_expression_run_with_no_input("empty") is True
205
+ assert can_expression_run_with_no_input("range(10)") is True
206
+ assert can_expression_run_with_no_input("range(1; 10)") is True
207
+
208
+ def test_array_literals(self) -> None:
209
+ """Test array literals without dots return True"""
210
+ assert can_expression_run_with_no_input("[1, 2, 3]") is True
211
+ assert can_expression_run_with_no_input('["a", "b", "c"]') is True
212
+ assert can_expression_run_with_no_input("[true, false]") is True
213
+ assert can_expression_run_with_no_input("[null, empty]") is True
214
+
215
+ def test_object_literals(self) -> None:
216
+ """Test object literals without dots return True"""
217
+ assert can_expression_run_with_no_input('{"key": "value"}') is True
218
+ assert can_expression_run_with_no_input('{"a": 1, "b": 2}') is True
219
+ assert can_expression_run_with_no_input('{"flag": true}') is True
220
+
221
+ def test_array_literals_with_dots(self) -> None:
222
+ """Test array literals with dots return False"""
223
+ assert can_expression_run_with_no_input("[.field, .other]") is False
224
+ assert can_expression_run_with_no_input('["a", .field]') is False
225
+
226
+ def test_object_literals_with_dots(self) -> None:
227
+ """Test object literals with dots return False"""
228
+ assert can_expression_run_with_no_input('{"key": .field}') is False
229
+ assert can_expression_run_with_no_input('{"a": .field, "b": .other}') is False
230
+
231
+ def test_string_operations(self) -> None:
232
+ """Test string operations without dots return True"""
233
+ assert can_expression_run_with_no_input('"hello" + "world"') is True
234
+ assert can_expression_run_with_no_input('"a" + "b" + "c"') is True
235
+
236
+ def test_number_operations(self) -> None:
237
+ """Test number operations return True"""
238
+ assert can_expression_run_with_no_input("1 + 2") is True
239
+ assert can_expression_run_with_no_input("3 * 4") is True
240
+ assert can_expression_run_with_no_input("10 / 2") is True
241
+ assert can_expression_run_with_no_input("5 - 3") is True
242
+
243
+ def test_decimal_number_operations(self) -> None:
244
+ """Test decimal number operations return True (ensures decimal points in numbers don't require input)"""
245
+ assert can_expression_run_with_no_input("3.14 + 2.5") is True
246
+ assert can_expression_run_with_no_input("3.14 * 2.5") is True
247
+ assert can_expression_run_with_no_input("10.5 / 2.5") is True
248
+ assert can_expression_run_with_no_input("5.5 - 3.2") is True
249
+ assert can_expression_run_with_no_input("3.14 + 2.5 * 1.5") is True
250
+ assert can_expression_run_with_no_input("3.14") is True
251
+ assert can_expression_run_with_no_input("-3.14") is True
252
+ assert can_expression_run_with_no_input("+3.14") is True
253
+
254
+ def test_mixed_operations(self) -> None:
255
+ """Test mixed operations without dots return True"""
256
+ assert can_expression_run_with_no_input('"hello" + 42') is True
257
+ assert can_expression_run_with_no_input('42 + "world"') is True
258
+
259
+ def test_operations_with_dots(self) -> None:
260
+ """Test operations with dots return False"""
261
+ assert can_expression_run_with_no_input(".field + .other") is False
262
+ assert can_expression_run_with_no_input('"hello" + .field') is False
263
+ assert can_expression_run_with_no_input('.field + "world"') is False
264
+
265
+ def test_whitespace_handling(self) -> None:
266
+ """Test whitespace handling"""
267
+ assert can_expression_run_with_no_input(" null ") is True
268
+ assert can_expression_run_with_no_input(" true ") is True
269
+ assert can_expression_run_with_no_input(" false ") is True
270
+ assert can_expression_run_with_no_input(" 42 ") is True
271
+ assert can_expression_run_with_no_input(' "hello" ') is True
272
+
273
+ def test_complex_nullary_expressions(self) -> None:
274
+ """Test complex nullary expressions"""
275
+ assert can_expression_run_with_no_input("range(1; 10; 2)") is True
276
+ assert can_expression_run_with_no_input("range(0; 100)") is True
277
+
278
+ def test_edge_cases(self) -> None:
279
+ """Test edge cases"""
280
+ # Empty array
281
+ assert can_expression_run_with_no_input("[]") is True
282
+ # Empty object
283
+ assert can_expression_run_with_no_input("{}") is True
284
+ # Nested arrays
285
+ assert can_expression_run_with_no_input("[[1, 2], [3, 4]]") is True
286
+ # Nested objects
287
+ assert can_expression_run_with_no_input('{"a": {"b": "c"}}') is True
288
+
289
+
290
+ class TestCanExpressionRunOnSingleItem:
291
+ """Test the _can_expression_run_on_single_item function"""
292
+
293
+ def test_empty_key(self) -> None:
294
+ """Test empty key returns False"""
295
+ assert _can_expression_run_on_single_item(".field", "") is False
296
+ assert _can_expression_run_on_single_item("map(.field)", "") is False
297
+
298
+ def test_key_at_start(self) -> None:
299
+ """Test key at start of expression"""
300
+ assert _can_expression_run_on_single_item(".item.field", "item") is True
301
+ assert _can_expression_run_on_single_item(".item", "item") is True
302
+
303
+ def test_key_in_middle(self) -> None:
304
+ """Test key in middle of expression"""
305
+ assert _can_expression_run_on_single_item(".data.item.field", "item") is False
306
+ assert _can_expression_run_on_single_item(".body.item.yaeli", "item") is False
307
+
308
+ def test_key_at_end(self) -> None:
309
+ """Test key at end of expression"""
310
+ assert _can_expression_run_on_single_item(".field.item", "item") is False
311
+
312
+ def test_key_in_function(self) -> None:
313
+ """Test key in function calls"""
314
+ assert _can_expression_run_on_single_item("map(.item.field)", "item") is True
315
+ assert (
316
+ _can_expression_run_on_single_item("select(.item.status)", "item") is True
317
+ )
318
+
319
+ def test_key_in_pipe(self) -> None:
320
+ """Test key in pipe operations"""
321
+ assert _can_expression_run_on_single_item(".[] | .item.field", "item") is True
322
+ assert (
323
+ _can_expression_run_on_single_item(".data[] | .item.field", "item") is True
324
+ )
325
+
326
+ def test_key_in_array(self) -> None:
327
+ """Test key in array literals"""
328
+ assert (
329
+ _can_expression_run_on_single_item("[.item.id, .item.name]", "item") is True
330
+ )
331
+ assert (
332
+ _can_expression_run_on_single_item(
333
+ "[.data.item.id, .body.item.name]", "item"
334
+ )
335
+ is False
336
+ )
337
+
338
+ def test_key_in_object(self) -> None:
339
+ """Test key in object literals"""
340
+ assert (
341
+ _can_expression_run_on_single_item(
342
+ "{id: .item.id, name: .item.name}", "item"
343
+ )
344
+ is True
345
+ )
346
+ assert (
347
+ _can_expression_run_on_single_item(
348
+ "{id: .data.item.id, name: .body.item.name}", "item"
349
+ )
350
+ is False
351
+ )
352
+
353
+ def test_key_in_conditional(self) -> None:
354
+ """Test key in conditional expressions"""
355
+ assert (
356
+ _can_expression_run_on_single_item(
357
+ "if .item.exists then .item.value else null end", "item"
358
+ )
359
+ is True
360
+ )
361
+ assert (
362
+ _can_expression_run_on_single_item(
363
+ "if .data.item.exists then .body.item.value else null end", "item"
364
+ )
365
+ is False
366
+ )
367
+
368
+ def test_key_in_string_ignored(self) -> None:
369
+ """Test key in string literals is ignored"""
370
+ assert (
371
+ _can_expression_run_on_single_item('"this is .item in string"', "item")
372
+ is False
373
+ )
374
+ assert (
375
+ _can_expression_run_on_single_item(
376
+ 'select(.data.string == ".item")', "item"
377
+ )
378
+ is False
379
+ )
380
+
381
+ def test_key_with_word_boundaries(self) -> None:
382
+ """Test key with proper word boundaries"""
383
+ assert _can_expression_run_on_single_item(".item.field", "item") is True
384
+ assert _can_expression_run_on_single_item(".item", "item") is True
385
+ # Should not match if key is part of larger word
386
+ assert _can_expression_run_on_single_item(".itemize.field", "item") is False
387
+ assert _can_expression_run_on_single_item(".items.field", "item") is False
388
+ assert _can_expression_run_on_single_item(".myitem.field", "item") is False
389
+
390
+ def test_key_case_sensitive(self) -> None:
391
+ """Test key matching is case sensitive"""
392
+ assert _can_expression_run_on_single_item(".ITEM.field", "item") is False
393
+ assert _can_expression_run_on_single_item(".Item.field", "item") is False
394
+ assert _can_expression_run_on_single_item(".ITEM.field", "ITEM") is True
395
+
396
+ def test_key_with_special_characters(self) -> None:
397
+ """Test key with special characters"""
398
+ assert _can_expression_run_on_single_item(".item[0].field", "item") is True
399
+ assert _can_expression_run_on_single_item(".item.field[0]", "item") is True
400
+
401
+ def test_key_not_present(self) -> None:
402
+ """Test key not present in expression"""
403
+ assert _can_expression_run_on_single_item(".field.other", "item") is False
404
+ assert _can_expression_run_on_single_item(".data.field", "item") is False
405
+ assert _can_expression_run_on_single_item("null", "item") is False
406
+ assert _can_expression_run_on_single_item('"hello"', "item") is False
407
+
408
+ def test_key_in_complex_expressions(self) -> None:
409
+ """Test key in complex expressions"""
410
+ assert (
411
+ _can_expression_run_on_single_item(".data.items[] | .item.field", "item")
412
+ is True
413
+ )
414
+ assert (
415
+ _can_expression_run_on_single_item(
416
+ "reduce .item.items[] as $item (0; . + $item.value)", "item"
417
+ )
418
+ is True
419
+ )
420
+ assert (
421
+ _can_expression_run_on_single_item("group_by(.item.category)", "item")
422
+ is True
423
+ )
424
+ assert (
425
+ _can_expression_run_on_single_item("sort_by(.item.priority)", "item")
426
+ is True
427
+ )
428
+ assert _can_expression_run_on_single_item("unique_by(.item.id)", "item") is True
429
+
430
+ def test_key_with_escaped_strings(self) -> None:
431
+ """Test key detection with escaped strings"""
432
+ assert (
433
+ _can_expression_run_on_single_item(
434
+ '"hello \\"world\\"" + .item.field', "item"
435
+ )
436
+ is True
437
+ )
438
+ assert (
439
+ _can_expression_run_on_single_item(
440
+ '"this is .item in string" + .other.field', "item"
441
+ )
442
+ is False
443
+ )
444
+
445
+
446
+ class TestClassifyInput:
447
+ """Test the classify_input function"""
448
+
449
+ def test_none_input(self) -> None:
450
+ """Test expressions that require no input"""
451
+ assert classify_input("null") == InputClassifyingResult.NONE
452
+ assert classify_input("true") == InputClassifyingResult.NONE
453
+ assert classify_input("false") == InputClassifyingResult.NONE
454
+ assert classify_input("42") == InputClassifyingResult.NONE
455
+ assert classify_input('"hello"') == InputClassifyingResult.NONE
456
+ assert classify_input("[1, 2, 3]") == InputClassifyingResult.NONE
457
+ assert classify_input('{"key": "value"}') == InputClassifyingResult.NONE
458
+ assert classify_input("empty") == InputClassifyingResult.NONE
459
+ assert classify_input("range(10)") == InputClassifyingResult.NONE
460
+ assert classify_input('"hello" + "world"') == InputClassifyingResult.NONE
461
+ assert classify_input("1 + 2") == InputClassifyingResult.NONE
462
+ assert classify_input("") == InputClassifyingResult.NONE
463
+ assert classify_input(" ") == InputClassifyingResult.NONE
464
+
465
+ def test_single_input(self) -> None:
466
+ """Test expressions that can run on single item"""
467
+ assert classify_input(".item.field", "item") == InputClassifyingResult.SINGLE
468
+ assert classify_input(".item", "item") == InputClassifyingResult.SINGLE
469
+ assert (
470
+ classify_input("map(.item.field)", "item") == InputClassifyingResult.SINGLE
471
+ )
472
+ assert (
473
+ classify_input("select(.item.status)", "item")
474
+ == InputClassifyingResult.SINGLE
475
+ )
476
+ assert (
477
+ classify_input(".[] | .item.field", "item") == InputClassifyingResult.SINGLE
478
+ )
479
+ assert (
480
+ classify_input("[.item.id, .item.name]", "item")
481
+ == InputClassifyingResult.SINGLE
482
+ )
483
+ assert (
484
+ classify_input("{id: .item.id, name: .item.name}", "item")
485
+ == InputClassifyingResult.SINGLE
486
+ )
487
+ assert (
488
+ classify_input("if .item.exists then .item.value else null end", "item")
489
+ == InputClassifyingResult.SINGLE
490
+ )
491
+
492
+ def test_all_input(self) -> None:
493
+ """Test expressions that require all input"""
494
+ assert classify_input(".field") == InputClassifyingResult.ALL
495
+ assert classify_input(".data.field") == InputClassifyingResult.ALL
496
+ assert classify_input("map(.field)") == InputClassifyingResult.ALL
497
+ assert classify_input("select(.field)") == InputClassifyingResult.ALL
498
+ assert classify_input(".[] | .field") == InputClassifyingResult.ALL
499
+ assert classify_input("[.field, .other]") == InputClassifyingResult.ALL
500
+ assert (
501
+ classify_input("{id: .field, name: .other}") == InputClassifyingResult.ALL
502
+ )
503
+ assert (
504
+ classify_input("if .field.exists then .other.value else null end")
505
+ == InputClassifyingResult.ALL
506
+ )
507
+
508
+ def test_single_input_without_key(self) -> None:
509
+ """Test single input expressions without key parameter"""
510
+ assert classify_input(".item.field") == InputClassifyingResult.ALL
511
+ assert classify_input("map(.item.field)") == InputClassifyingResult.ALL
512
+ assert classify_input("select(.item.status)") == InputClassifyingResult.ALL
513
+
514
+ def test_single_input_with_different_key(self) -> None:
515
+ """Test single input expressions with different key"""
516
+ assert classify_input(".item.field", "other") == InputClassifyingResult.ALL
517
+ assert classify_input(".data.item.field", "other") == InputClassifyingResult.ALL
518
+ assert classify_input("map(.item.field)", "other") == InputClassifyingResult.ALL
519
+
520
+ def test_edge_cases(self) -> None:
521
+ """Test edge cases"""
522
+ # Empty key
523
+ assert classify_input(".item.field", "") == InputClassifyingResult.ALL
524
+ # None key
525
+ assert classify_input(".item.field", None) == InputClassifyingResult.ALL
526
+ # Key in string (should not match)
527
+ assert (
528
+ classify_input('"this is .item in string"', "item")
529
+ == InputClassifyingResult.NONE
530
+ )
531
+ # Key with word boundaries
532
+ assert classify_input(".itemize.field", "item") == InputClassifyingResult.ALL
533
+ assert classify_input(".items.field", "item") == InputClassifyingResult.ALL
534
+ assert classify_input(".myitem.field", "item") == InputClassifyingResult.ALL
535
+
536
+ def test_complex_expressions(self) -> None:
537
+ """Test complex expressions"""
538
+ # Complex single input expressions
539
+ assert (
540
+ classify_input(".data.items[] | .item.field", "item")
541
+ == InputClassifyingResult.SINGLE
542
+ )
543
+ assert (
544
+ classify_input("reduce .item.items[] as $item (0; . + $item.value)", "item")
545
+ == InputClassifyingResult.SINGLE
546
+ )
547
+ assert (
548
+ classify_input("group_by(.item.category)", "item")
549
+ == InputClassifyingResult.SINGLE
550
+ )
551
+ assert (
552
+ classify_input("sort_by(.item.priority)", "item")
553
+ == InputClassifyingResult.SINGLE
554
+ )
555
+ assert (
556
+ classify_input("unique_by(.item.id)", "item")
557
+ == InputClassifyingResult.SINGLE
558
+ )
559
+
560
+ # Complex all input expressions
561
+ assert (
562
+ classify_input(".data.items[] | .field.item", "item")
563
+ == InputClassifyingResult.ALL
564
+ )
565
+ assert (
566
+ classify_input("reduce .data.items[] as $item (0; . + $item.value)", "item")
567
+ == InputClassifyingResult.ALL
568
+ )
569
+ assert (
570
+ classify_input("group_by(.data.category)", "item")
571
+ == InputClassifyingResult.ALL
572
+ )
573
+ assert (
574
+ classify_input("sort_by(.data.priority)", "item")
575
+ == InputClassifyingResult.ALL
576
+ )
577
+ assert (
578
+ classify_input("unique_by(.data.id)", "item") == InputClassifyingResult.ALL
579
+ )
580
+
581
+ def test_input_dependent_functions(self) -> None:
582
+ """Test input-dependent functions"""
583
+ # These should all require ALL input regardless of key
584
+ assert classify_input("map(.field)", "item") == InputClassifyingResult.ALL
585
+ assert classify_input("select(.field)", "item") == InputClassifyingResult.ALL
586
+ assert classify_input("length(.field)", "item") == InputClassifyingResult.ALL
587
+ assert classify_input("keys(.field)", "item") == InputClassifyingResult.ALL
588
+ assert classify_input("values(.field)", "item") == InputClassifyingResult.ALL
589
+ assert classify_input("has(.field)", "item") == InputClassifyingResult.ALL
590
+ assert classify_input("in(.field)", "item") == InputClassifyingResult.ALL
591
+ assert classify_input("index(.field)", "item") == InputClassifyingResult.ALL
592
+ assert classify_input("indices(.field)", "item") == InputClassifyingResult.ALL
593
+ assert classify_input("contains(.field)", "item") == InputClassifyingResult.ALL
594
+ assert classify_input("paths(.field)", "item") == InputClassifyingResult.ALL
595
+ assert (
596
+ classify_input("leaf_paths(.field)", "item") == InputClassifyingResult.ALL
597
+ )
598
+ assert (
599
+ classify_input("to_entries(.field)", "item") == InputClassifyingResult.ALL
600
+ )
601
+ assert (
602
+ classify_input("from_entries(.field)", "item") == InputClassifyingResult.ALL
603
+ )
604
+ assert (
605
+ classify_input("with_entries(.field)", "item") == InputClassifyingResult.ALL
606
+ )
607
+ assert classify_input("del(.field)", "item") == InputClassifyingResult.ALL
608
+ assert classify_input("delpaths(.field)", "item") == InputClassifyingResult.ALL
609
+ assert classify_input("walk(.field)", "item") == InputClassifyingResult.ALL
610
+ assert classify_input("reduce(.field)", "item") == InputClassifyingResult.ALL
611
+ assert classify_input("foreach(.field)", "item") == InputClassifyingResult.ALL
612
+ assert classify_input("input(.field)", "item") == InputClassifyingResult.ALL
613
+ assert classify_input("inputs(.field)", "item") == InputClassifyingResult.ALL
614
+ assert classify_input("limit(.field)", "item") == InputClassifyingResult.ALL
615
+ assert classify_input("first(.field)", "item") == InputClassifyingResult.ALL
616
+ assert classify_input("last(.field)", "item") == InputClassifyingResult.ALL
617
+ assert classify_input("nth(.field)", "item") == InputClassifyingResult.ALL
618
+ assert classify_input("while(.field)", "item") == InputClassifyingResult.ALL
619
+ assert classify_input("until(.field)", "item") == InputClassifyingResult.ALL
620
+ assert classify_input("recurse(.field)", "item") == InputClassifyingResult.ALL
621
+ assert (
622
+ classify_input("recurse_down(.field)", "item") == InputClassifyingResult.ALL
623
+ )
624
+ assert classify_input("bsearch(.field)", "item") == InputClassifyingResult.ALL
625
+ assert (
626
+ classify_input("combinations(.field)", "item") == InputClassifyingResult.ALL
627
+ )
628
+ assert (
629
+ classify_input("permutations(.field)", "item") == InputClassifyingResult.ALL
630
+ )
631
+
632
+ def test_whitespace_handling(self) -> None:
633
+ """Test whitespace handling"""
634
+ assert classify_input(" null ") == InputClassifyingResult.NONE
635
+ assert classify_input(" true ") == InputClassifyingResult.NONE
636
+ assert classify_input(" false ") == InputClassifyingResult.NONE
637
+ assert classify_input(" 42 ") == InputClassifyingResult.NONE
638
+ assert classify_input(' "hello" ') == InputClassifyingResult.NONE
639
+ assert (
640
+ classify_input(" .item.field ", "item") == InputClassifyingResult.SINGLE
641
+ )
642
+ assert classify_input(" .field ") == InputClassifyingResult.ALL
643
+
644
+ def test_string_operations(self) -> None:
645
+ """Test string operations"""
646
+ assert classify_input('"hello" + "world"') == InputClassifyingResult.NONE
647
+ assert classify_input('"hello" + .field') == InputClassifyingResult.ALL
648
+ assert classify_input('.field + "world"') == InputClassifyingResult.ALL
649
+ assert (
650
+ classify_input('.item.field + "world"', "item")
651
+ == InputClassifyingResult.SINGLE
652
+ )
653
+
654
+ def test_number_operations(self) -> None:
655
+ """Test number operations"""
656
+ assert classify_input("1 + 2") == InputClassifyingResult.NONE
657
+ assert classify_input("3 * 4") == InputClassifyingResult.NONE
658
+ assert classify_input("10 / 2") == InputClassifyingResult.NONE
659
+ assert classify_input("5 - 3") == InputClassifyingResult.NONE
660
+ assert classify_input(".field + 2") == InputClassifyingResult.ALL
661
+ assert (
662
+ classify_input(".item.field + 2", "item") == InputClassifyingResult.SINGLE
663
+ )
664
+
665
+ def test_array_operations(self) -> None:
666
+ """Test array operations"""
667
+ assert classify_input("[1, 2, 3]") == InputClassifyingResult.NONE
668
+ assert classify_input("[.field, .other]") == InputClassifyingResult.ALL
669
+ assert (
670
+ classify_input("[.item.field, .item.other]", "item")
671
+ == InputClassifyingResult.SINGLE
672
+ )
673
+ assert (
674
+ classify_input("[.data.item.field, .body.item.other]", "item")
675
+ == InputClassifyingResult.ALL
676
+ )
677
+
678
+ def test_object_operations(self) -> None:
679
+ """Test object operations"""
680
+ assert classify_input('{"key": "value"}') == InputClassifyingResult.NONE
681
+ assert classify_input('{"key": .field}') == InputClassifyingResult.ALL
682
+ assert (
683
+ classify_input('{"key": .item.field}', "item")
684
+ == InputClassifyingResult.SINGLE
685
+ )
686
+ assert (
687
+ classify_input('{"key": .data.item.field}', "item")
688
+ == InputClassifyingResult.ALL
689
+ )
690
+
691
+ def test_conditional_operations(self) -> None:
692
+ """Test conditional operations"""
693
+ assert (
694
+ classify_input("if true then 1 else 0 end") == InputClassifyingResult.NONE
695
+ )
696
+ assert (
697
+ classify_input("if .field then 1 else 0 end") == InputClassifyingResult.ALL
698
+ )
699
+ assert (
700
+ classify_input("if .item.field then 1 else 0 end", "item")
701
+ == InputClassifyingResult.SINGLE
702
+ )
703
+ assert (
704
+ classify_input("if .data.item.field then 1 else 0 end", "item")
705
+ == InputClassifyingResult.ALL
706
+ )
707
+
708
+ def test_range_operations(self) -> None:
709
+ """Test range operations"""
710
+ assert classify_input("range(10)") == InputClassifyingResult.NONE
711
+ assert classify_input("range(1; 10)") == InputClassifyingResult.NONE
712
+ assert classify_input("range(1; 10; 2)") == InputClassifyingResult.NONE
713
+ assert classify_input("range(.field)") == InputClassifyingResult.ALL
714
+ assert (
715
+ classify_input("range(.item.field)", "item")
716
+ == InputClassifyingResult.SINGLE
717
+ )
718
+
719
+ def test_complex_mixed_expressions(self) -> None:
720
+ """Test complex mixed expressions"""
721
+ # Mixed nullary expressions
722
+ assert classify_input('"hello" + 42') == InputClassifyingResult.NONE
723
+ assert classify_input('42 + "world"') == InputClassifyingResult.NONE
724
+ assert classify_input('[1, "hello", true]') == InputClassifyingResult.NONE
725
+
726
+ # Mixed single input expressions
727
+ assert (
728
+ classify_input('"hello" + .item.field', "item")
729
+ == InputClassifyingResult.SINGLE
730
+ )
731
+ assert (
732
+ classify_input('.item.field + "world"', "item")
733
+ == InputClassifyingResult.SINGLE
734
+ )
735
+ assert (
736
+ classify_input('[.item.id, "hello", .item.name]', "item")
737
+ == InputClassifyingResult.SINGLE
738
+ )
739
+
740
+ # Mixed all input expressions
741
+ assert classify_input('"hello" + .field') == InputClassifyingResult.ALL
742
+ assert classify_input('.field + "world"') == InputClassifyingResult.ALL
743
+ assert classify_input('[.field, "hello", .other]') == InputClassifyingResult.ALL
744
+
745
+
746
+ class TestInputClassifyingResult:
747
+ """Test the InputClassifyingResult enum"""
748
+
749
+ def test_enum_values(self) -> None:
750
+ """Test enum values"""
751
+ assert InputClassifyingResult.NONE.value == 1
752
+ assert InputClassifyingResult.SINGLE.value == 2
753
+ assert InputClassifyingResult.ALL.value == 3
754
+
755
+ def test_enum_names(self) -> None:
756
+ """Test enum names"""
757
+ assert InputClassifyingResult.NONE.name == "NONE"
758
+ assert InputClassifyingResult.SINGLE.name == "SINGLE"
759
+ assert InputClassifyingResult.ALL.name == "ALL"
760
+
761
+ def test_enum_comparison(self) -> None:
762
+ """Test enum comparison"""
763
+ assert InputClassifyingResult.NONE != InputClassifyingResult.SINGLE
764
+ assert InputClassifyingResult.SINGLE != InputClassifyingResult.ALL
765
+ assert InputClassifyingResult.NONE != InputClassifyingResult.ALL
766
+ assert InputClassifyingResult.NONE == InputClassifyingResult.NONE
767
+ assert InputClassifyingResult.SINGLE == InputClassifyingResult.SINGLE
768
+ assert InputClassifyingResult.ALL == InputClassifyingResult.ALL
769
+
770
+ def test_enum_string_representation(self) -> None:
771
+ """Test enum string representation"""
772
+ assert str(InputClassifyingResult.NONE) == "InputClassifyingResult.NONE"
773
+ assert str(InputClassifyingResult.SINGLE) == "InputClassifyingResult.SINGLE"
774
+ assert str(InputClassifyingResult.ALL) == "InputClassifyingResult.ALL"
775
+
776
+ def test_enum_repr(self) -> None:
777
+ """Test enum repr"""
778
+ assert repr(InputClassifyingResult.NONE) == "<InputClassifyingResult.NONE: 1>"
779
+ assert (
780
+ repr(InputClassifyingResult.SINGLE) == "<InputClassifyingResult.SINGLE: 2>"
781
+ )
782
+ assert repr(InputClassifyingResult.ALL) == "<InputClassifyingResult.ALL: 3>"
783
+
784
+
785
+ class TestIntegration:
786
+ """Integration tests for the jq_input_evaluator module"""
787
+
788
+ def test_real_world_scenarios(self) -> None:
789
+ """Test real-world scenarios"""
790
+ # Blueprint mapping scenarios
791
+ assert (
792
+ classify_input('"newRelicService"', "item") == InputClassifyingResult.NONE
793
+ )
794
+ assert (
795
+ classify_input('"newRelicService" in mapping', "item")
796
+ == InputClassifyingResult.ALL
797
+ )
798
+ assert (
799
+ classify_input('.item.blueprint == "newRelicService"', "item")
800
+ == InputClassifyingResult.SINGLE
801
+ )
802
+
803
+ # Entity property scenarios
804
+ assert classify_input(".item.name", "item") == InputClassifyingResult.SINGLE
805
+ assert (
806
+ classify_input(".item.description", "item") == InputClassifyingResult.SINGLE
807
+ )
808
+ assert classify_input(".item.status", "item") == InputClassifyingResult.SINGLE
809
+ assert classify_input(".data.name", "item") == InputClassifyingResult.ALL
810
+ assert classify_input(".data.description", "item") == InputClassifyingResult.ALL
811
+
812
+ # Selector scenarios
813
+ assert (
814
+ classify_input('.item.status == "active"', "item")
815
+ == InputClassifyingResult.SINGLE
816
+ )
817
+ assert (
818
+ classify_input('.item.type == "service"', "item")
819
+ == InputClassifyingResult.SINGLE
820
+ )
821
+ assert (
822
+ classify_input('.data.status == "active"', "item")
823
+ == InputClassifyingResult.ALL
824
+ )
825
+ assert (
826
+ classify_input('.data.type == "service"', "item")
827
+ == InputClassifyingResult.ALL
828
+ )
829
+
830
+ # Complex mapping scenarios
831
+ assert (
832
+ classify_input("map(.item.field)", "item") == InputClassifyingResult.SINGLE
833
+ )
834
+ assert (
835
+ classify_input('select(.item.status == "active")', "item")
836
+ == InputClassifyingResult.SINGLE
837
+ )
838
+ assert (
839
+ classify_input(".[] | .item.field", "item") == InputClassifyingResult.SINGLE
840
+ )
841
+ assert (
842
+ classify_input("[.item.id, .item.name]", "item")
843
+ == InputClassifyingResult.SINGLE
844
+ )
845
+ assert (
846
+ classify_input("{id: .item.id, name: .item.name}", "item")
847
+ == InputClassifyingResult.SINGLE
848
+ )
849
+
850
+ # Static value scenarios
851
+ assert classify_input('"static_value"', "item") == InputClassifyingResult.NONE
852
+ assert classify_input("null", "item") == InputClassifyingResult.NONE
853
+ assert classify_input("true", "item") == InputClassifyingResult.NONE
854
+ assert classify_input("false", "item") == InputClassifyingResult.NONE
855
+ assert classify_input("42", "item") == InputClassifyingResult.NONE
856
+
857
+ def test_performance_scenarios(self) -> None:
858
+ """Test performance-related scenarios"""
859
+ # Large expressions
860
+ large_expr = " + ".join([f'"{i}"' for i in range(100)])
861
+ assert classify_input(large_expr, "item") == InputClassifyingResult.NONE
862
+
863
+ # Complex nested expressions
864
+ complex_expr = "if .item.exists then .item.value else .item.default end"
865
+ assert classify_input(complex_expr, "item") == InputClassifyingResult.SINGLE
866
+
867
+ # Multiple function calls
868
+ multi_func_expr = "map(.item.field) | select(.item.status) | .item.value"
869
+ assert classify_input(multi_func_expr, "item") == InputClassifyingResult.SINGLE
870
+
871
+ def test_edge_case_scenarios(self) -> None:
872
+ """Test edge case scenarios"""
873
+ # Empty expressions
874
+ assert classify_input("", "item") == InputClassifyingResult.NONE
875
+ assert classify_input(" ", "item") == InputClassifyingResult.NONE
876
+
877
+ # Single character expressions
878
+ assert classify_input(".", "item") == InputClassifyingResult.ALL
879
+ assert classify_input("a", "item") == InputClassifyingResult.NONE
880
+
881
+ # Very long expressions
882
+ long_expr = " + ".join([f'"{i}"' for i in range(1000)])
883
+ assert classify_input(long_expr, "item") == InputClassifyingResult.NONE
884
+
885
+ # Expressions with special characters
886
+ assert classify_input('.item["field"]', "item") == InputClassifyingResult.SINGLE
887
+ assert (
888
+ classify_input('.item["field"]["subfield"]', "item")
889
+ == InputClassifyingResult.SINGLE
890
+ )
891
+ assert classify_input(".item.field[0]", "item") == InputClassifyingResult.SINGLE
892
+ assert (
893
+ classify_input(".item.field[0].subfield", "item")
894
+ == InputClassifyingResult.SINGLE
895
+ )
896
+
897
+ def test_consistency_scenarios(self) -> None:
898
+ """Test consistency scenarios"""
899
+ # Same expression with different keys should be consistent
900
+ expr = ".field.other"
901
+ assert classify_input(expr, "item") == InputClassifyingResult.ALL
902
+ assert classify_input(expr, "field") == InputClassifyingResult.SINGLE
903
+ assert classify_input(expr, "other") == InputClassifyingResult.ALL
904
+
905
+ # Same expression with different keys should be consistent
906
+ expr = ".data.item.field"
907
+ assert classify_input(expr, "item") == InputClassifyingResult.ALL
908
+ assert classify_input(expr, "data") == InputClassifyingResult.SINGLE
909
+ assert classify_input(expr, "field") == InputClassifyingResult.ALL
910
+
911
+ # Same expression with different keys should be consistent
912
+ expr = "map(.item.field)"
913
+ assert classify_input(expr, "item") == InputClassifyingResult.SINGLE
914
+ assert classify_input(expr, "field") == InputClassifyingResult.ALL
915
+ assert classify_input(expr, "other") == InputClassifyingResult.ALL
916
+
917
+ def test_error_handling_scenarios(self) -> None:
918
+ """Test error handling scenarios"""
919
+ # Invalid expressions should still be classified
920
+ assert classify_input(".invalid.field", "item") == InputClassifyingResult.ALL
921
+ assert classify_input("invalid(.field)", "item") == InputClassifyingResult.ALL
922
+ assert classify_input("invalid", "item") == InputClassifyingResult.NONE
923
+
924
+ # Malformed expressions should still be classified
925
+ assert classify_input("..field", "item") == InputClassifyingResult.ALL
926
+ assert classify_input(".field.", "item") == InputClassifyingResult.ALL
927
+ assert classify_input("field.", "item") == InputClassifyingResult.ALL
928
+
929
+ # Expressions with syntax errors should still be classified
930
+ assert classify_input(".field +", "item") == InputClassifyingResult.ALL
931
+ assert classify_input("+ .field", "item") == InputClassifyingResult.ALL
932
+ assert classify_input(".field + + .other", "item") == InputClassifyingResult.ALL