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.
- integrations/_infra/Dockerfile.Deb +1 -0
- integrations/_infra/Dockerfile.local +1 -0
- port_ocean/clients/port/mixins/integrations.py +1 -1
- port_ocean/core/handlers/entity_processor/jq_entity_processor.py +472 -17
- port_ocean/core/handlers/entity_processor/jq_input_evaluator.py +137 -0
- port_ocean/core/handlers/port_app_config/models.py +1 -1
- port_ocean/core/integrations/mixins/sync_raw.py +1 -1
- port_ocean/core/integrations/mixins/utils.py +241 -23
- port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +932 -1
- port_ocean/tests/core/handlers/entity_processor/test_jq_input_evaluator.py +932 -0
- port_ocean/tests/utils/test_cache.py +240 -0
- port_ocean/utils/cache.py +45 -9
- {port_ocean-0.28.11.dist-info → port_ocean-0.28.14.dist-info}/METADATA +1 -1
- {port_ocean-0.28.11.dist-info → port_ocean-0.28.14.dist-info}/RECORD +17 -15
- {port_ocean-0.28.11.dist-info → port_ocean-0.28.14.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.28.11.dist-info → port_ocean-0.28.14.dist-info}/WHEEL +0 -0
- {port_ocean-0.28.11.dist-info → port_ocean-0.28.14.dist-info}/entry_points.txt +0 -0
|
@@ -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
|