@@ -804,3 +804,329 @@ def test_flatten_allof_with_anyof_commands() -> None:
804804 'additionalProperties' : False ,
805805 }
806806 )
807+
808+
809+ def test_merge_additional_properties_multiple_dict_schemas () -> None :
810+ """Test merging when all additionalProperties are dict schemas (no False)."""
811+ schema : dict [str , Any ] = {
812+ 'type' : 'object' ,
813+ 'allOf' : [
814+ {
815+ 'type' : 'object' ,
816+ 'properties' : {'a' : {'type' : 'string' }},
817+ 'additionalProperties' : {'type' : 'string' },
818+ },
819+ {
820+ 'type' : 'object' ,
821+ 'properties' : {'b' : {'type' : 'string' }},
822+ 'additionalProperties' : {'type' : 'number' },
823+ },
824+ ],
825+ }
826+
827+ transformer = FlattenAllofTransformer (schema )
828+ flattened = transformer .walk ()
829+
830+ # Multiple dict schemas can't be easily merged, so return True
831+ assert flattened == snapshot (
832+ {
833+ 'type' : 'object' ,
834+ 'properties' : {'b' : {'type' : 'string' }},
835+ 'additionalProperties' : True ,
836+ }
837+ )
838+
839+
840+ def test_filter_by_restricted_property_sets_removes_properties () -> None :
841+ """Test that restricted property sets filter out properties not in intersection."""
842+ schema : dict [str , Any ] = {
843+ 'type' : 'object' ,
844+ 'allOf' : [
845+ {
846+ 'type' : 'object' ,
847+ 'properties' : {'a' : {'type' : 'string' }, 'b' : {'type' : 'string' }},
848+ 'additionalProperties' : False ,
849+ },
850+ {
851+ 'type' : 'object' ,
852+ 'properties' : {'b' : {'type' : 'string' }, 'c' : {'type' : 'string' }},
853+ 'additionalProperties' : False ,
854+ },
855+ ],
856+ }
857+
858+ transformer = FlattenAllofTransformer (schema )
859+ flattened = transformer .walk ()
860+
861+ # Only 'b' is in both restricted sets
862+ assert flattened == snapshot (
863+ {
864+ 'type' : 'object' ,
865+ 'properties' : {'b' : {'type' : 'string' }},
866+ 'additionalProperties' : False ,
867+ }
868+ )
869+
870+
871+ def test_filter_incompatible_properties_with_false_additional () -> None :
872+ """Test filtering properties when a member has additionalProperties: False."""
873+ schema : dict [str , Any ] = {
874+ 'type' : 'object' ,
875+ 'allOf' : [
876+ {
877+ 'type' : 'object' ,
878+ 'properties' : {'a' : {'type' : 'string' }},
879+ 'additionalProperties' : {'type' : 'string' },
880+ },
881+ {
882+ 'type' : 'object' ,
883+ 'properties' : {'b' : {'type' : 'integer' }},
884+ 'additionalProperties' : False ,
885+ },
886+ ],
887+ }
888+
889+ transformer = FlattenAllofTransformer (schema )
890+ flattened = transformer .walk ()
891+
892+ # 'a' is not in second member's properties and second has additionalProperties: False
893+ # 'b' is integer, not compatible with first member's additionalProperties: {'type': 'string'}
894+ # Result should be empty
895+ assert flattened == snapshot (
896+ {
897+ 'type' : 'object' ,
898+ 'additionalProperties' : False ,
899+ }
900+ )
901+
902+
903+ def test_filter_incompatible_properties_removes_all_properties () -> None :
904+ """Test that filtering incompatible properties can remove all properties."""
905+ schema : dict [str , Any ] = {
906+ 'type' : 'object' ,
907+ 'allOf' : [
908+ {
909+ 'type' : 'object' ,
910+ 'properties' : {'a' : {'type' : 'string' }},
911+ 'required' : ['a' ],
912+ 'additionalProperties' : {'type' : 'string' },
913+ },
914+ {
915+ 'type' : 'object' ,
916+ 'properties' : {'b' : {'type' : 'integer' }},
917+ 'required' : ['b' ],
918+ 'additionalProperties' : False ,
919+ },
920+ ],
921+ }
922+
923+ transformer = FlattenAllofTransformer (schema )
924+ flattened = transformer .walk ()
925+
926+ # All properties are incompatible, so both properties and required should be removed
927+ assert flattened == snapshot (
928+ {
929+ 'type' : 'object' ,
930+ 'additionalProperties' : False ,
931+ }
932+ )
933+
934+
935+ def test_merge_additional_properties_true_values () -> None :
936+ """Test merging when additionalProperties are True values (not False, not dict) - covers line 218."""
937+ schema : dict [str , Any ] = {
938+ 'type' : 'object' ,
939+ 'allOf' : [
940+ {
941+ 'type' : 'object' ,
942+ 'properties' : {'a' : {'type' : 'string' }},
943+ 'additionalProperties' : True , # Explicitly set to True
944+ },
945+ {
946+ 'type' : 'object' ,
947+ 'properties' : {'b' : {'type' : 'string' }},
948+ 'additionalProperties' : True , # Explicitly set to True
949+ },
950+ ],
951+ }
952+
953+ transformer = FlattenAllofTransformer (schema )
954+ flattened = transformer .walk ()
955+
956+ # When all values are True (not False, not dict), line 218 returns True
957+ assert flattened == snapshot (
958+ {
959+ 'type' : 'object' ,
960+ 'properties' : {
961+ 'a' : {'type' : 'string' },
962+ 'b' : {'type' : 'string' },
963+ },
964+ 'additionalProperties' : True ,
965+ }
966+ )
967+
968+
969+ def test_filter_by_restricted_property_sets_no_required () -> None :
970+ """Test filtering when properties exist but required doesn't."""
971+ schema : dict [str , Any ] = {
972+ 'type' : 'object' ,
973+ 'allOf' : [
974+ {
975+ 'type' : 'object' ,
976+ 'properties' : {'a' : {'type' : 'string' }, 'b' : {'type' : 'string' }},
977+ 'additionalProperties' : False ,
978+ },
979+ {
980+ 'type' : 'object' ,
981+ 'properties' : {'b' : {'type' : 'string' }, 'c' : {'type' : 'string' }},
982+ 'additionalProperties' : False ,
983+ },
984+ ],
985+ }
986+
987+ transformer = FlattenAllofTransformer (schema )
988+ flattened = transformer .walk ()
989+
990+ # Only 'b' is in both restricted sets, no required field
991+ assert flattened == snapshot (
992+ {
993+ 'type' : 'object' ,
994+ 'properties' : {'b' : {'type' : 'string' }},
995+ 'additionalProperties' : False ,
996+ }
997+ )
998+
999+
1000+ def test_filter_incompatible_properties_removes_required_only () -> None :
1001+ """Test that filtering incompatible properties can remove required while keeping some properties."""
1002+ schema : dict [str , Any ] = {
1003+ 'type' : 'object' ,
1004+ 'allOf' : [
1005+ {
1006+ 'type' : 'object' ,
1007+ 'properties' : {'a' : {'type' : 'string' }, 'b' : {'type' : 'string' }},
1008+ 'required' : ['a' , 'b' ],
1009+ 'additionalProperties' : {'type' : 'string' },
1010+ },
1011+ {
1012+ 'type' : 'object' ,
1013+ 'properties' : {'b' : {'type' : 'string' }},
1014+ 'required' : ['b' ],
1015+ 'additionalProperties' : False ,
1016+ },
1017+ ],
1018+ }
1019+
1020+ transformer = FlattenAllofTransformer (schema )
1021+ flattened = transformer .walk ()
1022+
1023+ # 'a' is incompatible (not in second member's properties, second has additionalProperties: False)
1024+ # 'b' is compatible (in both, both are strings)
1025+ # So 'a' should be removed from both properties and required
1026+ assert flattened == snapshot (
1027+ {
1028+ 'type' : 'object' ,
1029+ 'properties' : {'b' : {'type' : 'string' }},
1030+ 'required' : ['b' ],
1031+ 'additionalProperties' : False ,
1032+ }
1033+ )
1034+
1035+
1036+ def test_filter_incompatible_properties_removes_required_to_empty () -> None :
1037+ """Test that filtering incompatible properties can remove all required fields while keeping properties."""
1038+ schema : dict [str , Any ] = {
1039+ 'type' : 'object' ,
1040+ 'allOf' : [
1041+ {
1042+ 'type' : 'object' ,
1043+ 'properties' : {'a' : {'type' : 'string' }, 'b' : {'type' : 'string' }},
1044+ 'required' : ['a' ],
1045+ 'additionalProperties' : {'type' : 'string' },
1046+ },
1047+ {
1048+ 'type' : 'object' ,
1049+ 'properties' : {'b' : {'type' : 'string' }},
1050+ # No required field
1051+ 'additionalProperties' : False ,
1052+ },
1053+ ],
1054+ }
1055+
1056+ transformer = FlattenAllofTransformer (schema )
1057+ flattened = transformer .walk ()
1058+
1059+ # 'a' is incompatible (not in second member's properties, second has additionalProperties: False)
1060+ # 'b' is compatible (in both, both are strings)
1061+ # So 'a' should be removed from properties, and required should become empty and be removed
1062+ assert flattened == snapshot (
1063+ {
1064+ 'type' : 'object' ,
1065+ 'properties' : {'b' : {'type' : 'string' }},
1066+ 'additionalProperties' : False ,
1067+ }
1068+ )
1069+
1070+
1071+ def test_filter_incompatible_properties_with_list_type () -> None :
1072+ """Test filtering properties when additionalProperties has list type (covers _get_type_set with list)."""
1073+ schema : dict [str , Any ] = {
1074+ 'type' : 'object' ,
1075+ 'allOf' : [
1076+ {
1077+ 'type' : 'object' ,
1078+ 'properties' : {'a' : {'type' : 'string' }},
1079+ 'additionalProperties' : {'type' : ['string' , 'number' ]},
1080+ },
1081+ {
1082+ 'type' : 'object' ,
1083+ 'properties' : {'b' : {'type' : 'boolean' }},
1084+ 'additionalProperties' : False ,
1085+ },
1086+ ],
1087+ }
1088+
1089+ transformer = FlattenAllofTransformer (schema )
1090+ flattened = transformer .walk ()
1091+
1092+ # 'a' is string, compatible with ['string', 'number']
1093+ # 'b' is boolean, not compatible with ['string', 'number'], and second has additionalProperties: False
1094+ assert flattened == snapshot (
1095+ {
1096+ 'type' : 'object' ,
1097+ 'additionalProperties' : False ,
1098+ }
1099+ )
1100+
1101+
1102+ def test_filter_incompatible_properties_with_no_type_in_additional () -> None :
1103+ """Test filtering when additionalProperties schema has no type field (covers _get_type_set with no type)."""
1104+ schema : dict [str , Any ] = {
1105+ 'type' : 'object' ,
1106+ 'allOf' : [
1107+ {
1108+ 'type' : 'object' ,
1109+ 'properties' : {'a' : {'type' : 'string' }},
1110+ 'additionalProperties' : {'properties' : {'x' : {'type' : 'string' }}}, # No type field
1111+ },
1112+ {
1113+ 'type' : 'object' ,
1114+ 'properties' : {'b' : {'type' : 'string' }},
1115+ 'additionalProperties' : False ,
1116+ },
1117+ ],
1118+ }
1119+
1120+ transformer = FlattenAllofTransformer (schema )
1121+ flattened = transformer .walk ()
1122+
1123+ # When additionalProperties has no type, _get_type_set returns None, so type check passes
1124+ # But 'a' is not in second member's properties and second has additionalProperties: False
1125+ # 'b' is not in first member's properties and first's additionalProperties has no type (None)
1126+ assert flattened == snapshot (
1127+ {
1128+ 'type' : 'object' ,
1129+ 'properties' : {'b' : {'type' : 'string' }},
1130+ 'additionalProperties' : False ,
1131+ }
1132+ )
0 commit comments