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
|
@@ -50,7 +50,7 @@ class TestJQEntityProcessor:
|
|
|
50
50
|
raw_entity_mappings = {"foo": ".foo"}
|
|
51
51
|
selector_query = '.foo == "bar"'
|
|
52
52
|
result = await mocked_processor._get_mapped_entity(
|
|
53
|
-
data, raw_entity_mappings, selector_query
|
|
53
|
+
data, raw_entity_mappings, None, selector_query
|
|
54
54
|
)
|
|
55
55
|
assert result.entity == {"foo": "bar"}
|
|
56
56
|
assert result.did_entity_pass_selector is True
|
|
@@ -357,3 +357,934 @@ class TestJQEntityProcessor:
|
|
|
357
357
|
"{'blueprint': '.bar', 'identifier': '.foo'} (null, missing, or misconfigured)"
|
|
358
358
|
in logs_captured
|
|
359
359
|
)
|
|
360
|
+
|
|
361
|
+
async def test_build_raw_entity_mappings_string_values(
|
|
362
|
+
self, mocked_processor: JQEntityProcessor
|
|
363
|
+
) -> None:
|
|
364
|
+
"""Test _build_raw_entity_mappings with string values that evaluate to different InputEvaluationResult types"""
|
|
365
|
+
raw_entity_mappings = {
|
|
366
|
+
"identifier": ".item.id", # SINGLE - contains pattern
|
|
367
|
+
"title": ".item.name", # SINGLE - contains pattern
|
|
368
|
+
"blueprint": ".item.type", # SINGLE - contains pattern
|
|
369
|
+
"icon": ".item.icon", # SINGLE - contains pattern
|
|
370
|
+
"team": ".item.team", # SINGLE - contains pattern
|
|
371
|
+
"properties": {
|
|
372
|
+
"status": ".item.status", # SINGLE - contains pattern
|
|
373
|
+
"description": ".item.desc", # SINGLE - contains pattern
|
|
374
|
+
"external_ref": ".external.ref", # ALL - contains dots but not pattern
|
|
375
|
+
"static_value": '"static"', # NONE - no pattern, no dots
|
|
376
|
+
},
|
|
377
|
+
"relations": {
|
|
378
|
+
"owner": ".item.owner", # SINGLE - contains pattern
|
|
379
|
+
"parent": ".item.parent", # SINGLE - contains pattern
|
|
380
|
+
"external_relation": ".external.relation", # ALL - contains dots but not pattern
|
|
381
|
+
"null_value": "null", # NONE - nullary expression
|
|
382
|
+
},
|
|
383
|
+
}
|
|
384
|
+
items_to_parse_name = "item"
|
|
385
|
+
|
|
386
|
+
single, all_items, none = mocked_processor._build_raw_entity_mappings(
|
|
387
|
+
raw_entity_mappings, items_to_parse_name
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# SINGLE mappings should contain all fields that reference .item
|
|
391
|
+
expected_single = {
|
|
392
|
+
"identifier": ".item.id",
|
|
393
|
+
"title": ".item.name",
|
|
394
|
+
"blueprint": ".item.type",
|
|
395
|
+
"icon": ".item.icon",
|
|
396
|
+
"team": ".item.team",
|
|
397
|
+
"properties": {
|
|
398
|
+
"status": ".item.status",
|
|
399
|
+
"description": ".item.desc",
|
|
400
|
+
},
|
|
401
|
+
"relations": {
|
|
402
|
+
"owner": ".item.owner",
|
|
403
|
+
"parent": ".item.parent",
|
|
404
|
+
},
|
|
405
|
+
}
|
|
406
|
+
assert single == expected_single
|
|
407
|
+
|
|
408
|
+
# ALL mappings should contain fields that reference other patterns
|
|
409
|
+
expected_all = {
|
|
410
|
+
"properties": {
|
|
411
|
+
"external_ref": ".external.ref",
|
|
412
|
+
},
|
|
413
|
+
"relations": {
|
|
414
|
+
"external_relation": ".external.relation",
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
assert all_items == expected_all
|
|
418
|
+
|
|
419
|
+
# NONE mappings should contain nullary expressions
|
|
420
|
+
expected_none = {
|
|
421
|
+
"properties": {
|
|
422
|
+
"static_value": '"static"',
|
|
423
|
+
},
|
|
424
|
+
"relations": {
|
|
425
|
+
"null_value": "null",
|
|
426
|
+
},
|
|
427
|
+
}
|
|
428
|
+
assert none == expected_none
|
|
429
|
+
|
|
430
|
+
async def test_group_string_mapping_value(
|
|
431
|
+
self, mocked_processor: JQEntityProcessor
|
|
432
|
+
) -> None:
|
|
433
|
+
"""Test group_string_mapping_value function with various string values"""
|
|
434
|
+
from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
|
|
435
|
+
InputClassifyingResult,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# Test with different input evaluation results
|
|
439
|
+
mappings: dict[InputClassifyingResult, dict[str, Any]] = {
|
|
440
|
+
InputClassifyingResult.SINGLE: {},
|
|
441
|
+
InputClassifyingResult.ALL: {},
|
|
442
|
+
InputClassifyingResult.NONE: {},
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
# Test SINGLE evaluation (contains pattern)
|
|
446
|
+
mocked_processor.group_string_mapping_value(
|
|
447
|
+
"item", mappings, "identifier", ".item.id"
|
|
448
|
+
)
|
|
449
|
+
assert mappings[InputClassifyingResult.SINGLE]["identifier"] == ".item.id"
|
|
450
|
+
assert (
|
|
451
|
+
InputClassifyingResult.ALL not in mappings
|
|
452
|
+
or not mappings[InputClassifyingResult.ALL]
|
|
453
|
+
)
|
|
454
|
+
assert (
|
|
455
|
+
InputClassifyingResult.NONE not in mappings
|
|
456
|
+
or not mappings[InputClassifyingResult.NONE]
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
# Test ALL evaluation (contains dots but not pattern)
|
|
460
|
+
mappings = {
|
|
461
|
+
InputClassifyingResult.SINGLE: {},
|
|
462
|
+
InputClassifyingResult.ALL: {},
|
|
463
|
+
InputClassifyingResult.NONE: {},
|
|
464
|
+
}
|
|
465
|
+
mocked_processor.group_string_mapping_value(
|
|
466
|
+
"item", mappings, "external_ref", ".external.ref"
|
|
467
|
+
)
|
|
468
|
+
assert mappings[InputClassifyingResult.ALL]["external_ref"] == ".external.ref"
|
|
469
|
+
assert (
|
|
470
|
+
InputClassifyingResult.SINGLE not in mappings
|
|
471
|
+
or not mappings[InputClassifyingResult.SINGLE]
|
|
472
|
+
)
|
|
473
|
+
assert (
|
|
474
|
+
InputClassifyingResult.NONE not in mappings
|
|
475
|
+
or not mappings[InputClassifyingResult.NONE]
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# Test NONE evaluation (nullary expression)
|
|
479
|
+
mappings = {
|
|
480
|
+
InputClassifyingResult.SINGLE: {},
|
|
481
|
+
InputClassifyingResult.ALL: {},
|
|
482
|
+
InputClassifyingResult.NONE: {},
|
|
483
|
+
}
|
|
484
|
+
mocked_processor.group_string_mapping_value(
|
|
485
|
+
"item", mappings, "static_value", '"static"'
|
|
486
|
+
)
|
|
487
|
+
assert mappings[InputClassifyingResult.NONE]["static_value"] == '"static"'
|
|
488
|
+
assert (
|
|
489
|
+
InputClassifyingResult.SINGLE not in mappings
|
|
490
|
+
or not mappings[InputClassifyingResult.SINGLE]
|
|
491
|
+
)
|
|
492
|
+
assert (
|
|
493
|
+
InputClassifyingResult.ALL not in mappings
|
|
494
|
+
or not mappings[InputClassifyingResult.ALL]
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
async def test_group_complex_mapping_value_properties(
|
|
498
|
+
self, mocked_processor: JQEntityProcessor
|
|
499
|
+
) -> None:
|
|
500
|
+
"""Test group_complex_mapping_value with properties dictionary"""
|
|
501
|
+
from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
|
|
502
|
+
InputClassifyingResult,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
mappings: dict[InputClassifyingResult, dict[str, Any]] = {
|
|
506
|
+
InputClassifyingResult.SINGLE: {},
|
|
507
|
+
InputClassifyingResult.ALL: {},
|
|
508
|
+
InputClassifyingResult.NONE: {},
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
# Test properties with mixed string values
|
|
512
|
+
properties = {
|
|
513
|
+
"name": ".item.name", # SINGLE
|
|
514
|
+
"description": ".item.desc", # SINGLE
|
|
515
|
+
"external_ref": ".external.ref", # ALL
|
|
516
|
+
"static_value": '"static"', # NONE
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
mocked_processor.group_complex_mapping_value(
|
|
520
|
+
"item", mappings, "properties", properties
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
expected_single = {
|
|
524
|
+
"name": ".item.name",
|
|
525
|
+
"description": ".item.desc",
|
|
526
|
+
}
|
|
527
|
+
expected_all = {
|
|
528
|
+
"external_ref": ".external.ref",
|
|
529
|
+
}
|
|
530
|
+
expected_none = {
|
|
531
|
+
"static_value": '"static"',
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
assert mappings[InputClassifyingResult.SINGLE]["properties"] == expected_single
|
|
535
|
+
assert mappings[InputClassifyingResult.ALL]["properties"] == expected_all
|
|
536
|
+
assert mappings[InputClassifyingResult.NONE]["properties"] == expected_none
|
|
537
|
+
|
|
538
|
+
async def test_build_raw_entity_mappings_edge_cases(
|
|
539
|
+
self, mocked_processor: JQEntityProcessor
|
|
540
|
+
) -> None:
|
|
541
|
+
"""Test _build_raw_entity_mappings with edge cases including patterns in the middle of expressions"""
|
|
542
|
+
raw_entity_mappings: dict[str, Any] = {
|
|
543
|
+
"identifier": ".item.id", # Normal case - SINGLE
|
|
544
|
+
"title": "", # Empty string - NONE
|
|
545
|
+
"blueprint": " ", # Whitespace only - NONE
|
|
546
|
+
"icon": ".", # Just a dot - ALL
|
|
547
|
+
"team": ".item", # Just the pattern - SINGLE
|
|
548
|
+
"properties": {
|
|
549
|
+
"multiple_patterns": ".item.field.item", # Multiple occurrences - SINGLE
|
|
550
|
+
"pattern_at_end": ".field.item", # Pattern at end - ALL (doesn't start with .item)
|
|
551
|
+
"pattern_at_start": ".item.field", # Pattern at start - SINGLE
|
|
552
|
+
"pattern_in_middle": ".body.somefield.item", # Pattern in middle - ALL (doesn't start with .item)
|
|
553
|
+
"pattern_in_middle_with_dots": ".data.items.item.field", # Pattern in middle with dots - ALL
|
|
554
|
+
"case_sensitive": ".ITEM.field", # Case sensitive (should not match) - ALL
|
|
555
|
+
"special_chars": ".item.field[0]", # Special characters - SINGLE
|
|
556
|
+
"quoted_pattern": '".item.field"', # Quoted pattern - NONE
|
|
557
|
+
"field_with_null_name": ".is_null", # Field with null name - ALL
|
|
558
|
+
"empty_string": "", # Empty string - NONE
|
|
559
|
+
"item_in_string": 'select(.data.string == ".item")', # Item referenced in string only - ALL
|
|
560
|
+
"function_with_pattern": "map(.item.field)", # Function with pattern - SINGLE
|
|
561
|
+
"function_with_middle_pattern": "map(.body.item.field)", # Function with middle pattern - ALL
|
|
562
|
+
"select_with_pattern": 'select(.item.status == "active")', # Select with pattern - SINGLE
|
|
563
|
+
"select_with_middle_pattern": 'select(.data.item.status == "active")', # Select with middle pattern - ALL
|
|
564
|
+
"pipe_with_pattern": ".[] | .item.field", # Pipe with pattern - SINGLE
|
|
565
|
+
"pipe_with_middle_pattern": ".[] | .body.item.field", # Pipe with middle pattern - ALL
|
|
566
|
+
"array_with_pattern": "[.item.id, .item.name]", # Array with pattern - SINGLE
|
|
567
|
+
"array_with_middle_pattern": "[.data.item.id, .body.item.name]", # Array with middle pattern - ALL
|
|
568
|
+
"object_with_pattern": "{id: .item.id, name: .item.name}", # Object with pattern - SINGLE
|
|
569
|
+
"object_with_middle_pattern": "{id: .data.item.id, name: .body.item.name}", # Object with middle pattern - ALL
|
|
570
|
+
"nested_with_pattern": ".data.items[] | .item.field", # Nested with pattern - SINGLE
|
|
571
|
+
"nested_with_middle_pattern": ".data.items[] | .body.item.field", # Nested with middle pattern - ALL
|
|
572
|
+
"conditional_with_pattern": "if .item.exists then .item.value else null end", # Conditional with pattern - SINGLE
|
|
573
|
+
"conditional_with_middle_pattern": "if .data.item.exists then .body.item.value else null end", # Conditional with middle pattern - ALL
|
|
574
|
+
"string_plus_string": '"abc" + "def"', # String plus string - NONE
|
|
575
|
+
"number_plus_number": "42 + 10", # Number plus number - NONE
|
|
576
|
+
},
|
|
577
|
+
"relations": {
|
|
578
|
+
"normal_relation": ".item.owner", # Normal case - SINGLE
|
|
579
|
+
"middle_pattern_relation": ".data.item.owner", # Middle pattern - ALL
|
|
580
|
+
"external_relation": ".external.ref", # External reference - ALL
|
|
581
|
+
"nullary_relation": "null", # Nullary expression - NONE
|
|
582
|
+
},
|
|
583
|
+
}
|
|
584
|
+
items_to_parse_name = "item"
|
|
585
|
+
|
|
586
|
+
single, all_items, none = mocked_processor._build_raw_entity_mappings(
|
|
587
|
+
raw_entity_mappings, items_to_parse_name
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
# SINGLE mappings - only those that start with the exact pattern
|
|
591
|
+
expected_single = {
|
|
592
|
+
"identifier": ".item.id",
|
|
593
|
+
"team": ".item",
|
|
594
|
+
"properties": {
|
|
595
|
+
"multiple_patterns": ".item.field.item",
|
|
596
|
+
"pattern_at_start": ".item.field",
|
|
597
|
+
"special_chars": ".item.field[0]",
|
|
598
|
+
"function_with_pattern": "map(.item.field)",
|
|
599
|
+
"select_with_pattern": 'select(.item.status == "active")',
|
|
600
|
+
"pipe_with_pattern": ".[] | .item.field",
|
|
601
|
+
"array_with_pattern": "[.item.id, .item.name]",
|
|
602
|
+
"object_with_pattern": "{id: .item.id, name: .item.name}",
|
|
603
|
+
"nested_with_pattern": ".data.items[] | .item.field",
|
|
604
|
+
"conditional_with_pattern": "if .item.exists then .item.value else null end",
|
|
605
|
+
},
|
|
606
|
+
"relations": {
|
|
607
|
+
"normal_relation": ".item.owner",
|
|
608
|
+
},
|
|
609
|
+
}
|
|
610
|
+
assert single == expected_single
|
|
611
|
+
|
|
612
|
+
# ALL mappings - those with dots but not starting with the pattern
|
|
613
|
+
expected_all = {
|
|
614
|
+
"icon": ".",
|
|
615
|
+
"properties": {
|
|
616
|
+
"pattern_at_end": ".field.item",
|
|
617
|
+
"pattern_in_middle": ".body.somefield.item",
|
|
618
|
+
"pattern_in_middle_with_dots": ".data.items.item.field",
|
|
619
|
+
"case_sensitive": ".ITEM.field",
|
|
620
|
+
"function_with_middle_pattern": "map(.body.item.field)",
|
|
621
|
+
"select_with_middle_pattern": 'select(.data.item.status == "active")',
|
|
622
|
+
"item_in_string": 'select(.data.string == ".item")',
|
|
623
|
+
"pipe_with_middle_pattern": ".[] | .body.item.field",
|
|
624
|
+
"array_with_middle_pattern": "[.data.item.id, .body.item.name]",
|
|
625
|
+
"object_with_middle_pattern": "{id: .data.item.id, name: .body.item.name}",
|
|
626
|
+
"nested_with_middle_pattern": ".data.items[] | .body.item.field",
|
|
627
|
+
"conditional_with_middle_pattern": "if .data.item.exists then .body.item.value else null end",
|
|
628
|
+
"field_with_null_name": ".is_null",
|
|
629
|
+
},
|
|
630
|
+
"relations": {
|
|
631
|
+
"middle_pattern_relation": ".data.item.owner",
|
|
632
|
+
"external_relation": ".external.ref",
|
|
633
|
+
},
|
|
634
|
+
}
|
|
635
|
+
assert all_items == expected_all
|
|
636
|
+
|
|
637
|
+
# NONE mappings - nullary expressions
|
|
638
|
+
expected_none = {
|
|
639
|
+
"title": "",
|
|
640
|
+
"blueprint": " ",
|
|
641
|
+
"properties": {
|
|
642
|
+
"quoted_pattern": '".item.field"',
|
|
643
|
+
"empty_string": "",
|
|
644
|
+
"string_plus_string": '"abc" + "def"',
|
|
645
|
+
"number_plus_number": "42 + 10",
|
|
646
|
+
},
|
|
647
|
+
"relations": {
|
|
648
|
+
"nullary_relation": "null",
|
|
649
|
+
},
|
|
650
|
+
}
|
|
651
|
+
assert none == expected_none
|
|
652
|
+
|
|
653
|
+
async def test_build_raw_entity_mappings_complex_jq_expressions(
|
|
654
|
+
self, mocked_processor: JQEntityProcessor
|
|
655
|
+
) -> None:
|
|
656
|
+
"""Test _build_raw_entity_mappings with complex JQ expressions that contain the pattern but don't start with it"""
|
|
657
|
+
raw_entity_mappings: dict[str, Any] = {
|
|
658
|
+
"identifier": ".item.id", # Simple case - SINGLE
|
|
659
|
+
"title": ".item.name", # Simple case - SINGLE
|
|
660
|
+
"blueprint": ".item.type", # Simple case - SINGLE
|
|
661
|
+
"icon": ".item.icon", # Simple case - SINGLE
|
|
662
|
+
"team": ".item.team", # Simple case - SINGLE
|
|
663
|
+
"properties": {
|
|
664
|
+
# JQ expressions with functions that contain .item
|
|
665
|
+
"mapped_property": "map(.item.field)", # Contains .item but starts with map - SINGLE
|
|
666
|
+
"selected_property": 'select(.item.status == "active")', # Contains .item but starts with select - SINGLE
|
|
667
|
+
"filtered_property": '.[] | select(.item.type == "service")', # Contains .item in pipe - SINGLE
|
|
668
|
+
"array_literal": "[.item.id, .item.name]", # Contains .item in array - SINGLE
|
|
669
|
+
"object_literal": "{id: .item.id, name: .item.name}", # Contains .item in object - SINGLE
|
|
670
|
+
"nested_access": ".data.items[] | .item.field", # Contains .item in nested access - SINGLE
|
|
671
|
+
"conditional": "if .item.exists then .item.value else null end", # Contains .item in conditional - SINGLE
|
|
672
|
+
"function_call": "length(.item.array)", # Contains .item in function call - SINGLE
|
|
673
|
+
"range_expression": "range(.item.start; .item.end)", # Contains .item in range - SINGLE
|
|
674
|
+
"reduce_expression": "reduce .item.items[] as $item (0; . + $item.value)", # Contains .item in reduce - SINGLE
|
|
675
|
+
"group_by": "group_by(.item.category)", # Contains .item in group_by - SINGLE
|
|
676
|
+
"sort_by": "sort_by(.item.priority)", # Contains .item in sort_by - SINGLE
|
|
677
|
+
"unique_by": "unique_by(.item.id)", # Contains .item in unique_by - SINGLE
|
|
678
|
+
"flatten": "flatten(.item.nested)", # Contains .item in flatten - SINGLE
|
|
679
|
+
"transpose": "transpose(.item.matrix)", # Contains .item in transpose - SINGLE
|
|
680
|
+
"combinations": "combinations(.item.items)", # Contains .item in combinations - SINGLE
|
|
681
|
+
"permutations": "permutations(.item.items)", # Contains .item in permutations - SINGLE
|
|
682
|
+
"bsearch": "bsearch(.item.target)", # Contains .item in bsearch - SINGLE
|
|
683
|
+
"while_loop": "while(.item.condition; .item.update)", # Contains .item in while - SINGLE
|
|
684
|
+
"until_loop": "until(.item.condition; .item.update)", # Contains .item in until - SINGLE
|
|
685
|
+
"recurse": "recurse(.item.children)", # Contains .item in recurse - SINGLE
|
|
686
|
+
"paths": "paths(.item.structure)", # Contains .item in paths - SINGLE
|
|
687
|
+
"leaf_paths": "leaf_paths(.item.tree)", # Contains .item in leaf_paths - SINGLE
|
|
688
|
+
"keys": "keys(.item.object)", # Contains .item in keys - SINGLE
|
|
689
|
+
"values": "values(.item.object)", # Contains .item in values - SINGLE
|
|
690
|
+
"to_entries": "to_entries(.item.object)", # Contains .item in to_entries - SINGLE
|
|
691
|
+
"from_entries": "from_entries(.item.array)", # Contains .item in from_entries - SINGLE
|
|
692
|
+
"with_entries": "with_entries(.item.transformation)", # Contains .item in with_entries - SINGLE
|
|
693
|
+
"del": "del(.item.field)", # Contains .item in del - SINGLE
|
|
694
|
+
"delpaths": "delpaths(.item.paths)", # Contains .item in delpaths - SINGLE
|
|
695
|
+
"walk": "walk(.item.transformation)", # Contains .item in walk - SINGLE
|
|
696
|
+
"limit": "limit(.item.count; .item.items)", # Contains .item in limit - SINGLE
|
|
697
|
+
"first": "first(.item.items)", # Contains .item in first - SINGLE
|
|
698
|
+
"last": "last(.item.items)", # Contains .item in last - SINGLE
|
|
699
|
+
"nth": "nth(.item.index; .item.items)", # Contains .item in nth - SINGLE
|
|
700
|
+
"input": "input(.item.stream)", # Contains .item in input - SINGLE
|
|
701
|
+
"inputs": "inputs(.item.streams)", # Contains .item in inputs - SINGLE
|
|
702
|
+
"foreach": "foreach(.item.items) as $item (0; . + $item.value)", # Contains .item in foreach - SINGLE
|
|
703
|
+
"explode": "explode(.item.string)", # Contains .item in explode - SINGLE
|
|
704
|
+
"implode": "implode(.item.codes)", # Contains .item in implode - SINGLE
|
|
705
|
+
"split": "split(.item.delimiter; .item.string)", # Contains .item in split - SINGLE
|
|
706
|
+
"join": "join(.item.delimiter; .item.array)", # Contains .item in join - SINGLE
|
|
707
|
+
"add": "add(.item.numbers)", # Contains .item in add - SINGLE
|
|
708
|
+
"has": "has(.item.key; .item.object)", # Contains .item in has - SINGLE
|
|
709
|
+
"in": "in(.item.value; .item.array)", # Contains .item in in - SINGLE
|
|
710
|
+
"index": "index(.item.value; .item.array)", # Contains .item in index - SINGLE
|
|
711
|
+
"indices": "indices(.item.value; .item.array)", # Contains .item in indices - SINGLE
|
|
712
|
+
"contains": "contains(.item.value; .item.array)", # Contains .item in contains - SINGLE
|
|
713
|
+
"startswith": "startswith(.item.prefix; .item.string)", # Contains .item in startswith - SINGLE
|
|
714
|
+
"endswith": "endswith(.item.suffix; .item.string)", # Contains .item in endswith - SINGLE
|
|
715
|
+
"ltrimstr": "ltrimstr(.item.prefix; .item.string)", # Contains .item in ltrimstr - SINGLE
|
|
716
|
+
"rtrimstr": "rtrimstr(.item.suffix; .item.string)", # Contains .item in rtrimstr - SINGLE
|
|
717
|
+
"sub": "sub(.item.pattern; .item.replacement; .item.string)", # Contains .item in sub - SINGLE
|
|
718
|
+
"gsub": "gsub(.item.pattern; .item.replacement; .item.string)", # Contains .item in gsub - SINGLE
|
|
719
|
+
"test": "test(.item.pattern; .item.string)", # Contains .item in test - SINGLE
|
|
720
|
+
"match": "match(.item.pattern; .item.string)", # Contains .item in match - SINGLE
|
|
721
|
+
"capture": "capture(.item.pattern; .item.string)", # Contains .item in capture - SINGLE
|
|
722
|
+
"scan": "scan(.item.pattern; .item.string)", # Contains .item in scan - SINGLE
|
|
723
|
+
"split_on": "split_on(.item.delimiter; .item.string)", # Contains .item in split_on - SINGLE
|
|
724
|
+
"join_on": "join_on(.item.delimiter; .item.array)", # Contains .item in join_on - SINGLE
|
|
725
|
+
"tonumber": "tonumber(.item.string)", # Contains .item in tonumber - SINGLE
|
|
726
|
+
"tostring": "tostring(.item.number)", # Contains .item in tostring - SINGLE
|
|
727
|
+
"type": "type(.item.value)", # Contains .item in type - SINGLE
|
|
728
|
+
"isnan": "isnan(.item.number)", # Contains .item in isnan - SINGLE
|
|
729
|
+
"isinfinite": "isinfinite(.item.number)", # Contains .item in isinfinite - SINGLE
|
|
730
|
+
"isfinite": "isfinite(.item.number)", # Contains .item in isfinite - SINGLE
|
|
731
|
+
"isnormal": "isnormal(.item.number)", # Contains .item in isnormal - SINGLE
|
|
732
|
+
"floor": "floor(.item.number)", # Contains .item in floor - SINGLE
|
|
733
|
+
"ceil": "ceil(.item.number)", # Contains .item in ceil - SINGLE
|
|
734
|
+
"round": "round(.item.number)", # Contains .item in round - SINGLE
|
|
735
|
+
"sqrt": "sqrt(.item.number)", # Contains .item in sqrt - SINGLE
|
|
736
|
+
"sin": "sin(.item.angle)", # Contains .item in sin - SINGLE
|
|
737
|
+
"cos": "cos(.item.angle)", # Contains .item in cos - SINGLE
|
|
738
|
+
"tan": "tan(.item.angle)", # Contains .item in tan - SINGLE
|
|
739
|
+
"asin": "asin(.item.value)", # Contains .item in asin - SINGLE
|
|
740
|
+
"acos": "acos(.item.value)", # Contains .item in acos - SINGLE
|
|
741
|
+
"atan": "atan(.item.value)", # Contains .item in atan - SINGLE
|
|
742
|
+
"atan2": "atan2(.item.y; .item.x)", # Contains .item in atan2 - SINGLE
|
|
743
|
+
"log": "log(.item.number)", # Contains .item in log - SINGLE
|
|
744
|
+
"log10": "log10(.item.number)", # Contains .item in log10 - SINGLE
|
|
745
|
+
"log2": "log2(.item.number)", # Contains .item in log2 - SINGLE
|
|
746
|
+
"exp": "exp(.item.number)", # Contains .item in exp - SINGLE
|
|
747
|
+
"exp10": "exp10(.item.number)", # Contains .item in exp10 - SINGLE
|
|
748
|
+
"exp2": "exp2(.item.number)", # Contains .item in exp2 - SINGLE
|
|
749
|
+
"pow": "pow(.item.base; .item.exponent)", # Contains .item in pow - SINGLE
|
|
750
|
+
"fma": "fma(.item.x; .item.y; .item.z)", # Contains .item in fma - SINGLE
|
|
751
|
+
"fmod": "fmod(.item.x; .item.y)", # Contains .item in fmod - SINGLE
|
|
752
|
+
"remainder": "remainder(.item.x; .item.y)", # Contains .item in remainder - SINGLE
|
|
753
|
+
"drem": "drem(.item.x; .item.y)", # Contains .item in drem - SINGLE
|
|
754
|
+
"fabs": "fabs(.item.number)", # Contains .item in fabs - SINGLE
|
|
755
|
+
"fmax": "fmax(.item.x; .item.y)", # Contains .item in fmax - SINGLE
|
|
756
|
+
"fmin": "fmin(.item.x; .item.y)", # Contains .item in fmin - SINGLE
|
|
757
|
+
"fdim": "fdim(.item.x; .item.y)", # Contains .item in fdim - SINGLE
|
|
758
|
+
# Expressions that don't contain .item (should go to ALL or NONE)
|
|
759
|
+
"external_map": "map(.external.field)", # Doesn't contain .item - ALL
|
|
760
|
+
"external_select": 'select(.external.status == "active")', # Doesn't contain .item - ALL
|
|
761
|
+
"external_array": "[.external.id, .external.name]", # Doesn't contain .item - ALL
|
|
762
|
+
"static_value": '"static"', # Static value - NONE
|
|
763
|
+
"nullary_expression": "null", # Nullary expression - NONE
|
|
764
|
+
"boolean_expression": "true", # Boolean expression - NONE
|
|
765
|
+
"number_expression": "42", # Number expression - NONE
|
|
766
|
+
"string_expression": '"hello"', # String expression - NONE
|
|
767
|
+
"array_expression": "[1,2,3]", # Array expression - NONE
|
|
768
|
+
"object_expression": '{"key": "value"}', # Object expression - NONE
|
|
769
|
+
},
|
|
770
|
+
"relations": {
|
|
771
|
+
"mapped_relation": "map(.item.relation)", # Contains .item - SINGLE
|
|
772
|
+
"selected_relation": 'select(.item.relation == "active")', # Contains .item - SINGLE
|
|
773
|
+
"external_relation": "map(.external.relation)", # Doesn't contain .item - ALL
|
|
774
|
+
"static_relation": '"static"', # Static value - NONE
|
|
775
|
+
},
|
|
776
|
+
}
|
|
777
|
+
items_to_parse_name = "item"
|
|
778
|
+
|
|
779
|
+
single, all_items, none = mocked_processor._build_raw_entity_mappings(
|
|
780
|
+
raw_entity_mappings, items_to_parse_name
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
# SINGLE mappings - all expressions that contain .item
|
|
784
|
+
expected_single = {
|
|
785
|
+
"identifier": ".item.id",
|
|
786
|
+
"title": ".item.name",
|
|
787
|
+
"blueprint": ".item.type",
|
|
788
|
+
"icon": ".item.icon",
|
|
789
|
+
"team": ".item.team",
|
|
790
|
+
"properties": {
|
|
791
|
+
"mapped_property": "map(.item.field)",
|
|
792
|
+
"selected_property": 'select(.item.status == "active")',
|
|
793
|
+
"filtered_property": '.[] | select(.item.type == "service")',
|
|
794
|
+
"array_literal": "[.item.id, .item.name]",
|
|
795
|
+
"object_literal": "{id: .item.id, name: .item.name}",
|
|
796
|
+
"nested_access": ".data.items[] | .item.field",
|
|
797
|
+
"conditional": "if .item.exists then .item.value else null end",
|
|
798
|
+
"function_call": "length(.item.array)",
|
|
799
|
+
"range_expression": "range(.item.start; .item.end)",
|
|
800
|
+
"reduce_expression": "reduce .item.items[] as $item (0; . + $item.value)",
|
|
801
|
+
"group_by": "group_by(.item.category)",
|
|
802
|
+
"sort_by": "sort_by(.item.priority)",
|
|
803
|
+
"unique_by": "unique_by(.item.id)",
|
|
804
|
+
"flatten": "flatten(.item.nested)",
|
|
805
|
+
"transpose": "transpose(.item.matrix)",
|
|
806
|
+
"combinations": "combinations(.item.items)",
|
|
807
|
+
"permutations": "permutations(.item.items)",
|
|
808
|
+
"bsearch": "bsearch(.item.target)",
|
|
809
|
+
"while_loop": "while(.item.condition; .item.update)",
|
|
810
|
+
"until_loop": "until(.item.condition; .item.update)",
|
|
811
|
+
"recurse": "recurse(.item.children)",
|
|
812
|
+
"paths": "paths(.item.structure)",
|
|
813
|
+
"leaf_paths": "leaf_paths(.item.tree)",
|
|
814
|
+
"keys": "keys(.item.object)",
|
|
815
|
+
"values": "values(.item.object)",
|
|
816
|
+
"to_entries": "to_entries(.item.object)",
|
|
817
|
+
"from_entries": "from_entries(.item.array)",
|
|
818
|
+
"with_entries": "with_entries(.item.transformation)",
|
|
819
|
+
"del": "del(.item.field)",
|
|
820
|
+
"delpaths": "delpaths(.item.paths)",
|
|
821
|
+
"walk": "walk(.item.transformation)",
|
|
822
|
+
"limit": "limit(.item.count; .item.items)",
|
|
823
|
+
"first": "first(.item.items)",
|
|
824
|
+
"last": "last(.item.items)",
|
|
825
|
+
"nth": "nth(.item.index; .item.items)",
|
|
826
|
+
"input": "input(.item.stream)",
|
|
827
|
+
"inputs": "inputs(.item.streams)",
|
|
828
|
+
"foreach": "foreach(.item.items) as $item (0; . + $item.value)",
|
|
829
|
+
"explode": "explode(.item.string)",
|
|
830
|
+
"implode": "implode(.item.codes)",
|
|
831
|
+
"split": "split(.item.delimiter; .item.string)",
|
|
832
|
+
"join": "join(.item.delimiter; .item.array)",
|
|
833
|
+
"add": "add(.item.numbers)",
|
|
834
|
+
"has": "has(.item.key; .item.object)",
|
|
835
|
+
"in": "in(.item.value; .item.array)",
|
|
836
|
+
"index": "index(.item.value; .item.array)",
|
|
837
|
+
"indices": "indices(.item.value; .item.array)",
|
|
838
|
+
"contains": "contains(.item.value; .item.array)",
|
|
839
|
+
"startswith": "startswith(.item.prefix; .item.string)",
|
|
840
|
+
"endswith": "endswith(.item.suffix; .item.string)",
|
|
841
|
+
"ltrimstr": "ltrimstr(.item.prefix; .item.string)",
|
|
842
|
+
"rtrimstr": "rtrimstr(.item.suffix; .item.string)",
|
|
843
|
+
"sub": "sub(.item.pattern; .item.replacement; .item.string)",
|
|
844
|
+
"gsub": "gsub(.item.pattern; .item.replacement; .item.string)",
|
|
845
|
+
"test": "test(.item.pattern; .item.string)",
|
|
846
|
+
"match": "match(.item.pattern; .item.string)",
|
|
847
|
+
"capture": "capture(.item.pattern; .item.string)",
|
|
848
|
+
"scan": "scan(.item.pattern; .item.string)",
|
|
849
|
+
"split_on": "split_on(.item.delimiter; .item.string)",
|
|
850
|
+
"join_on": "join_on(.item.delimiter; .item.array)",
|
|
851
|
+
"tonumber": "tonumber(.item.string)",
|
|
852
|
+
"tostring": "tostring(.item.number)",
|
|
853
|
+
"type": "type(.item.value)",
|
|
854
|
+
"isnan": "isnan(.item.number)",
|
|
855
|
+
"isinfinite": "isinfinite(.item.number)",
|
|
856
|
+
"isfinite": "isfinite(.item.number)",
|
|
857
|
+
"isnormal": "isnormal(.item.number)",
|
|
858
|
+
"floor": "floor(.item.number)",
|
|
859
|
+
"ceil": "ceil(.item.number)",
|
|
860
|
+
"round": "round(.item.number)",
|
|
861
|
+
"sqrt": "sqrt(.item.number)",
|
|
862
|
+
"sin": "sin(.item.angle)",
|
|
863
|
+
"cos": "cos(.item.angle)",
|
|
864
|
+
"tan": "tan(.item.angle)",
|
|
865
|
+
"asin": "asin(.item.value)",
|
|
866
|
+
"acos": "acos(.item.value)",
|
|
867
|
+
"atan": "atan(.item.value)",
|
|
868
|
+
"atan2": "atan2(.item.y; .item.x)",
|
|
869
|
+
"log": "log(.item.number)",
|
|
870
|
+
"log10": "log10(.item.number)",
|
|
871
|
+
"log2": "log2(.item.number)",
|
|
872
|
+
"exp": "exp(.item.number)",
|
|
873
|
+
"exp10": "exp10(.item.number)",
|
|
874
|
+
"exp2": "exp2(.item.number)",
|
|
875
|
+
"pow": "pow(.item.base; .item.exponent)",
|
|
876
|
+
"fma": "fma(.item.x; .item.y; .item.z)",
|
|
877
|
+
"fmod": "fmod(.item.x; .item.y)",
|
|
878
|
+
"remainder": "remainder(.item.x; .item.y)",
|
|
879
|
+
"drem": "drem(.item.x; .item.y)",
|
|
880
|
+
"fabs": "fabs(.item.number)",
|
|
881
|
+
"fmax": "fmax(.item.x; .item.y)",
|
|
882
|
+
"fmin": "fmin(.item.x; .item.y)",
|
|
883
|
+
"fdim": "fdim(.item.x; .item.y)",
|
|
884
|
+
},
|
|
885
|
+
"relations": {
|
|
886
|
+
"mapped_relation": "map(.item.relation)",
|
|
887
|
+
"selected_relation": 'select(.item.relation == "active")',
|
|
888
|
+
},
|
|
889
|
+
}
|
|
890
|
+
assert single == expected_single
|
|
891
|
+
|
|
892
|
+
# ALL mappings - expressions with dots but not containing .item
|
|
893
|
+
expected_all = {
|
|
894
|
+
"properties": {
|
|
895
|
+
"external_map": "map(.external.field)",
|
|
896
|
+
"external_select": 'select(.external.status == "active")',
|
|
897
|
+
"external_array": "[.external.id, .external.name]",
|
|
898
|
+
},
|
|
899
|
+
"relations": {
|
|
900
|
+
"external_relation": "map(.external.relation)",
|
|
901
|
+
},
|
|
902
|
+
}
|
|
903
|
+
assert all_items == expected_all
|
|
904
|
+
|
|
905
|
+
# NONE mappings - nullary expressions and static values
|
|
906
|
+
expected_none = {
|
|
907
|
+
"properties": {
|
|
908
|
+
"static_value": '"static"',
|
|
909
|
+
"nullary_expression": "null",
|
|
910
|
+
"boolean_expression": "true",
|
|
911
|
+
"number_expression": "42",
|
|
912
|
+
"string_expression": '"hello"',
|
|
913
|
+
"array_expression": "[1,2,3]",
|
|
914
|
+
"object_expression": '{"key": "value"}',
|
|
915
|
+
},
|
|
916
|
+
"relations": {
|
|
917
|
+
"static_relation": '"static"',
|
|
918
|
+
},
|
|
919
|
+
}
|
|
920
|
+
assert none == expected_none
|
|
921
|
+
|
|
922
|
+
async def test_group_complex_mapping_value_relations(
|
|
923
|
+
self, mocked_processor: JQEntityProcessor
|
|
924
|
+
) -> None:
|
|
925
|
+
"""Test group_complex_mapping_value with relations dictionary"""
|
|
926
|
+
from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
|
|
927
|
+
InputClassifyingResult,
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
mappings: dict[InputClassifyingResult, dict[str, Any]] = {
|
|
931
|
+
InputClassifyingResult.SINGLE: {},
|
|
932
|
+
InputClassifyingResult.ALL: {},
|
|
933
|
+
InputClassifyingResult.NONE: {},
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
# Test relations with mixed string and IngestSearchQuery values
|
|
937
|
+
relations = {
|
|
938
|
+
"owner": ".item.owner", # String - SINGLE
|
|
939
|
+
"parent": { # IngestSearchQuery - SINGLE
|
|
940
|
+
"combinator": "and",
|
|
941
|
+
"rules": [
|
|
942
|
+
{
|
|
943
|
+
"property": "parent",
|
|
944
|
+
"operator": "equals",
|
|
945
|
+
"value": ".item.parent",
|
|
946
|
+
}
|
|
947
|
+
],
|
|
948
|
+
},
|
|
949
|
+
"external_relation": { # IngestSearchQuery - ALL
|
|
950
|
+
"combinator": "and",
|
|
951
|
+
"rules": [
|
|
952
|
+
{
|
|
953
|
+
"property": "external",
|
|
954
|
+
"operator": "equals",
|
|
955
|
+
"value": ".external.ref",
|
|
956
|
+
}
|
|
957
|
+
],
|
|
958
|
+
},
|
|
959
|
+
"static_relation": '"static"', # String - NONE
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
mocked_processor.group_complex_mapping_value(
|
|
963
|
+
"item", mappings, "relations", relations
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
expected_single = {
|
|
967
|
+
"owner": ".item.owner",
|
|
968
|
+
"parent": {
|
|
969
|
+
"combinator": "and",
|
|
970
|
+
"rules": [
|
|
971
|
+
{
|
|
972
|
+
"property": "parent",
|
|
973
|
+
"operator": "equals",
|
|
974
|
+
"value": ".item.parent",
|
|
975
|
+
}
|
|
976
|
+
],
|
|
977
|
+
},
|
|
978
|
+
}
|
|
979
|
+
expected_all = {
|
|
980
|
+
"external_relation": {
|
|
981
|
+
"combinator": "and",
|
|
982
|
+
"rules": [
|
|
983
|
+
{
|
|
984
|
+
"property": "external",
|
|
985
|
+
"operator": "equals",
|
|
986
|
+
"value": ".external.ref",
|
|
987
|
+
}
|
|
988
|
+
],
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
expected_none = {
|
|
992
|
+
"static_relation": '"static"',
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
assert mappings[InputClassifyingResult.SINGLE]["relations"] == expected_single
|
|
996
|
+
assert mappings[InputClassifyingResult.ALL]["relations"] == expected_all
|
|
997
|
+
assert mappings[InputClassifyingResult.NONE]["relations"] == expected_none
|
|
998
|
+
|
|
999
|
+
async def test_group_complex_mapping_value_identifier_ingest_search_query(
|
|
1000
|
+
self, mocked_processor: JQEntityProcessor
|
|
1001
|
+
) -> None:
|
|
1002
|
+
"""Test group_complex_mapping_value with identifier IngestSearchQuery"""
|
|
1003
|
+
from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
|
|
1004
|
+
InputClassifyingResult,
|
|
1005
|
+
)
|
|
1006
|
+
|
|
1007
|
+
mappings: dict[InputClassifyingResult, dict[str, Any]] = {
|
|
1008
|
+
InputClassifyingResult.SINGLE: {},
|
|
1009
|
+
InputClassifyingResult.ALL: {},
|
|
1010
|
+
InputClassifyingResult.NONE: {},
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
# Test identifier IngestSearchQuery that matches pattern
|
|
1014
|
+
identifier_query = {
|
|
1015
|
+
"combinator": "and",
|
|
1016
|
+
"rules": [{"property": "id", "operator": "equals", "value": ".item.id"}],
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
mocked_processor.group_complex_mapping_value(
|
|
1020
|
+
"item", mappings, "identifier", identifier_query
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
expected_single = {
|
|
1024
|
+
"combinator": "and",
|
|
1025
|
+
"rules": [{"property": "id", "operator": "equals", "value": ".item.id"}],
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
assert mappings[InputClassifyingResult.SINGLE]["identifier"] == expected_single
|
|
1029
|
+
assert (
|
|
1030
|
+
InputClassifyingResult.ALL not in mappings
|
|
1031
|
+
or not mappings[InputClassifyingResult.ALL]
|
|
1032
|
+
)
|
|
1033
|
+
assert (
|
|
1034
|
+
InputClassifyingResult.NONE not in mappings
|
|
1035
|
+
or not mappings[InputClassifyingResult.NONE]
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
async def test_group_complex_mapping_value_team_ingest_search_query(
|
|
1039
|
+
self, mocked_processor: JQEntityProcessor
|
|
1040
|
+
) -> None:
|
|
1041
|
+
"""Test group_complex_mapping_value with team IngestSearchQuery"""
|
|
1042
|
+
from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
|
|
1043
|
+
InputClassifyingResult,
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
mappings: dict[InputClassifyingResult, dict[str, Any]] = {
|
|
1047
|
+
InputClassifyingResult.SINGLE: {},
|
|
1048
|
+
InputClassifyingResult.ALL: {},
|
|
1049
|
+
InputClassifyingResult.NONE: {},
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
# Test team IngestSearchQuery that doesn't match pattern
|
|
1053
|
+
team_query = {
|
|
1054
|
+
"combinator": "and",
|
|
1055
|
+
"rules": [
|
|
1056
|
+
{"property": "team", "operator": "equals", "value": ".data.team"}
|
|
1057
|
+
],
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
mocked_processor.group_complex_mapping_value(
|
|
1061
|
+
"item", mappings, "team", team_query
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
expected_all = {
|
|
1065
|
+
"combinator": "and",
|
|
1066
|
+
"rules": [
|
|
1067
|
+
{"property": "team", "operator": "equals", "value": ".data.team"}
|
|
1068
|
+
],
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
assert mappings[InputClassifyingResult.ALL]["team"] == expected_all
|
|
1072
|
+
assert (
|
|
1073
|
+
InputClassifyingResult.SINGLE not in mappings
|
|
1074
|
+
or not mappings[InputClassifyingResult.SINGLE]
|
|
1075
|
+
)
|
|
1076
|
+
assert (
|
|
1077
|
+
InputClassifyingResult.NONE not in mappings
|
|
1078
|
+
or not mappings[InputClassifyingResult.NONE]
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
async def test_group_complex_mapping_value_nested_ingest_search_query(
|
|
1082
|
+
self, mocked_processor: JQEntityProcessor
|
|
1083
|
+
) -> None:
|
|
1084
|
+
"""Test group_complex_mapping_value with nested IngestSearchQuery"""
|
|
1085
|
+
from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
|
|
1086
|
+
InputClassifyingResult,
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
mappings: dict[InputClassifyingResult, dict[str, Any]] = {
|
|
1090
|
+
InputClassifyingResult.SINGLE: {},
|
|
1091
|
+
InputClassifyingResult.ALL: {},
|
|
1092
|
+
InputClassifyingResult.NONE: {},
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
# Test nested IngestSearchQuery with mixed rules
|
|
1096
|
+
nested_query = {
|
|
1097
|
+
"combinator": "and",
|
|
1098
|
+
"rules": [
|
|
1099
|
+
{
|
|
1100
|
+
"property": "field",
|
|
1101
|
+
"operator": "equals",
|
|
1102
|
+
"value": ".item.field", # SINGLE - contains pattern
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
"combinator": "or",
|
|
1106
|
+
"rules": [
|
|
1107
|
+
{
|
|
1108
|
+
"property": "external",
|
|
1109
|
+
"operator": "equals",
|
|
1110
|
+
"value": ".external.ref", # ALL - doesn't contain pattern
|
|
1111
|
+
}
|
|
1112
|
+
],
|
|
1113
|
+
},
|
|
1114
|
+
],
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
mocked_processor.group_complex_mapping_value(
|
|
1118
|
+
"item", mappings, "identifier", nested_query
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1121
|
+
# Should go to SINGLE because it contains at least one rule with the pattern
|
|
1122
|
+
expected_single = {
|
|
1123
|
+
"combinator": "and",
|
|
1124
|
+
"rules": [
|
|
1125
|
+
{"property": "field", "operator": "equals", "value": ".item.field"},
|
|
1126
|
+
{
|
|
1127
|
+
"combinator": "or",
|
|
1128
|
+
"rules": [
|
|
1129
|
+
{
|
|
1130
|
+
"property": "external",
|
|
1131
|
+
"operator": "equals",
|
|
1132
|
+
"value": ".external.ref",
|
|
1133
|
+
}
|
|
1134
|
+
],
|
|
1135
|
+
},
|
|
1136
|
+
],
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
assert mappings[InputClassifyingResult.SINGLE]["identifier"] == expected_single
|
|
1140
|
+
assert (
|
|
1141
|
+
InputClassifyingResult.ALL not in mappings
|
|
1142
|
+
or not mappings[InputClassifyingResult.ALL]
|
|
1143
|
+
)
|
|
1144
|
+
assert (
|
|
1145
|
+
InputClassifyingResult.NONE not in mappings
|
|
1146
|
+
or not mappings[InputClassifyingResult.NONE]
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
async def test_group_complex_mapping_value_invalid_ingest_search_query(
|
|
1150
|
+
self, mocked_processor: JQEntityProcessor
|
|
1151
|
+
) -> None:
|
|
1152
|
+
"""Test group_complex_mapping_value with invalid IngestSearchQuery structures"""
|
|
1153
|
+
from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
|
|
1154
|
+
InputClassifyingResult,
|
|
1155
|
+
)
|
|
1156
|
+
|
|
1157
|
+
mappings: dict[InputClassifyingResult, dict[str, Any]] = {
|
|
1158
|
+
InputClassifyingResult.SINGLE: {},
|
|
1159
|
+
InputClassifyingResult.ALL: {},
|
|
1160
|
+
InputClassifyingResult.NONE: {},
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
# Test invalid IngestSearchQuery (no rules field)
|
|
1164
|
+
invalid_query = {
|
|
1165
|
+
"combinator": "and"
|
|
1166
|
+
# Missing rules field
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
mocked_processor.group_complex_mapping_value(
|
|
1170
|
+
".item", mappings, "identifier", invalid_query
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
# Should go to ALL since it doesn't match the pattern
|
|
1174
|
+
expected_all = {"combinator": "and"}
|
|
1175
|
+
|
|
1176
|
+
assert mappings[InputClassifyingResult.ALL]["identifier"] == expected_all
|
|
1177
|
+
assert (
|
|
1178
|
+
InputClassifyingResult.SINGLE not in mappings
|
|
1179
|
+
or not mappings[InputClassifyingResult.SINGLE]
|
|
1180
|
+
)
|
|
1181
|
+
assert (
|
|
1182
|
+
InputClassifyingResult.NONE not in mappings
|
|
1183
|
+
or not mappings[InputClassifyingResult.NONE]
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
async def test_group_complex_mapping_value_empty_dict(
|
|
1187
|
+
self, mocked_processor: JQEntityProcessor
|
|
1188
|
+
) -> None:
|
|
1189
|
+
"""Test group_complex_mapping_value with empty dictionary"""
|
|
1190
|
+
from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
|
|
1191
|
+
InputClassifyingResult,
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
mappings: dict[InputClassifyingResult, dict[str, Any]] = {
|
|
1195
|
+
InputClassifyingResult.SINGLE: {},
|
|
1196
|
+
InputClassifyingResult.ALL: {},
|
|
1197
|
+
InputClassifyingResult.NONE: {},
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
# Test empty properties dictionary
|
|
1201
|
+
empty_properties: dict[str, Any] = {}
|
|
1202
|
+
|
|
1203
|
+
mocked_processor.group_complex_mapping_value(
|
|
1204
|
+
".item", mappings, "properties", empty_properties
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1207
|
+
# Should not add anything to any mapping
|
|
1208
|
+
assert (
|
|
1209
|
+
InputClassifyingResult.SINGLE not in mappings
|
|
1210
|
+
or not mappings[InputClassifyingResult.SINGLE]
|
|
1211
|
+
)
|
|
1212
|
+
assert (
|
|
1213
|
+
InputClassifyingResult.ALL not in mappings
|
|
1214
|
+
or not mappings[InputClassifyingResult.ALL]
|
|
1215
|
+
)
|
|
1216
|
+
assert (
|
|
1217
|
+
InputClassifyingResult.NONE not in mappings
|
|
1218
|
+
or not mappings[InputClassifyingResult.NONE]
|
|
1219
|
+
)
|
|
1220
|
+
|
|
1221
|
+
async def test_group_complex_mapping_value_mixed_content(
|
|
1222
|
+
self, mocked_processor: JQEntityProcessor
|
|
1223
|
+
) -> None:
|
|
1224
|
+
"""Test group_complex_mapping_value with mixed string and IngestSearchQuery content"""
|
|
1225
|
+
from port_ocean.core.handlers.entity_processor.jq_input_evaluator import (
|
|
1226
|
+
InputClassifyingResult,
|
|
1227
|
+
)
|
|
1228
|
+
|
|
1229
|
+
mappings: dict[InputClassifyingResult, dict[str, Any]] = {
|
|
1230
|
+
InputClassifyingResult.SINGLE: {},
|
|
1231
|
+
InputClassifyingResult.ALL: {},
|
|
1232
|
+
InputClassifyingResult.NONE: {},
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
# Test properties with mixed content
|
|
1236
|
+
mixed_properties = {
|
|
1237
|
+
"string_single": ".item.name", # String - SINGLE
|
|
1238
|
+
"string_all": ".external.ref", # String - ALL
|
|
1239
|
+
"string_none": '"static"', # String - NONE
|
|
1240
|
+
"query_single": { # IngestSearchQuery - SINGLE
|
|
1241
|
+
"combinator": "and",
|
|
1242
|
+
"rules": [
|
|
1243
|
+
{"property": "field", "operator": "equals", "value": ".item.field"}
|
|
1244
|
+
],
|
|
1245
|
+
},
|
|
1246
|
+
"query_all": { # IngestSearchQuery - ALL
|
|
1247
|
+
"combinator": "and",
|
|
1248
|
+
"rules": [
|
|
1249
|
+
{
|
|
1250
|
+
"property": "external",
|
|
1251
|
+
"operator": "equals",
|
|
1252
|
+
"value": ".external.field",
|
|
1253
|
+
}
|
|
1254
|
+
],
|
|
1255
|
+
},
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
mocked_processor.group_complex_mapping_value(
|
|
1259
|
+
"item", mappings, "properties", mixed_properties
|
|
1260
|
+
)
|
|
1261
|
+
|
|
1262
|
+
expected_single = {
|
|
1263
|
+
"string_single": ".item.name",
|
|
1264
|
+
"query_single": {
|
|
1265
|
+
"combinator": "and",
|
|
1266
|
+
"rules": [
|
|
1267
|
+
{"property": "field", "operator": "equals", "value": ".item.field"}
|
|
1268
|
+
],
|
|
1269
|
+
},
|
|
1270
|
+
}
|
|
1271
|
+
expected_all = {
|
|
1272
|
+
"string_all": ".external.ref",
|
|
1273
|
+
"query_all": {
|
|
1274
|
+
"combinator": "and",
|
|
1275
|
+
"rules": [
|
|
1276
|
+
{
|
|
1277
|
+
"property": "external",
|
|
1278
|
+
"operator": "equals",
|
|
1279
|
+
"value": ".external.field",
|
|
1280
|
+
}
|
|
1281
|
+
],
|
|
1282
|
+
},
|
|
1283
|
+
}
|
|
1284
|
+
expected_none = {
|
|
1285
|
+
"string_none": '"static"',
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
assert mappings[InputClassifyingResult.SINGLE]["properties"] == expected_single
|
|
1289
|
+
assert mappings[InputClassifyingResult.ALL]["properties"] == expected_all
|
|
1290
|
+
assert mappings[InputClassifyingResult.NONE]["properties"] == expected_none
|