schemathesis 4.3.15__py3-none-any.whl → 4.3.17__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 schemathesis might be problematic. Click here for more details.
- schemathesis/auths.py +24 -3
- schemathesis/checks.py +1 -1
- schemathesis/cli/commands/run/handlers/cassettes.py +1 -2
- schemathesis/cli/commands/run/handlers/output.py +5 -2
- schemathesis/config/_error.py +1 -1
- schemathesis/core/errors.py +30 -0
- schemathesis/engine/errors.py +12 -0
- schemathesis/engine/phases/unit/__init__.py +2 -2
- schemathesis/engine/phases/unit/_executor.py +4 -0
- schemathesis/generation/coverage.py +143 -50
- schemathesis/generation/hypothesis/builder.py +28 -7
- schemathesis/generation/meta.py +77 -2
- schemathesis/pytest/lazy.py +58 -12
- schemathesis/pytest/plugin.py +2 -2
- schemathesis/specs/openapi/_hypothesis.py +18 -98
- schemathesis/specs/openapi/adapter/parameters.py +181 -11
- schemathesis/specs/openapi/checks.py +5 -7
- schemathesis/specs/openapi/converter.py +1 -14
- schemathesis/specs/openapi/examples.py +4 -4
- schemathesis/specs/openapi/references.py +31 -1
- schemathesis/specs/openapi/schemas.py +5 -4
- schemathesis/transport/prepare.py +4 -3
- {schemathesis-4.3.15.dist-info → schemathesis-4.3.17.dist-info}/METADATA +6 -5
- {schemathesis-4.3.15.dist-info → schemathesis-4.3.17.dist-info}/RECORD +27 -27
- {schemathesis-4.3.15.dist-info → schemathesis-4.3.17.dist-info}/WHEEL +0 -0
- {schemathesis-4.3.15.dist-info → schemathesis-4.3.17.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.3.15.dist-info → schemathesis-4.3.17.dist-info}/licenses/LICENSE +0 -0
|
@@ -38,6 +38,7 @@ from schemathesis.core.transforms import deepclone
|
|
|
38
38
|
from schemathesis.core.validation import contains_unicode_surrogate_pair, has_invalid_characters, is_latin_1_encodable
|
|
39
39
|
from schemathesis.generation import GenerationMode
|
|
40
40
|
from schemathesis.generation.hypothesis import examples
|
|
41
|
+
from schemathesis.generation.meta import CoverageScenario
|
|
41
42
|
from schemathesis.openapi.generation.filters import is_invalid_path_parameter
|
|
42
43
|
|
|
43
44
|
from ..specs.openapi.converter import update_pattern_in_schema
|
|
@@ -93,17 +94,19 @@ UNKNOWN_PROPERTY_VALUE = 42
|
|
|
93
94
|
class GeneratedValue:
|
|
94
95
|
value: Any
|
|
95
96
|
generation_mode: GenerationMode
|
|
97
|
+
scenario: CoverageScenario
|
|
96
98
|
description: str
|
|
97
99
|
parameter: str | None
|
|
98
100
|
location: str | None
|
|
99
101
|
|
|
100
|
-
__slots__ = ("value", "generation_mode", "description", "parameter", "location")
|
|
102
|
+
__slots__ = ("value", "generation_mode", "scenario", "description", "parameter", "location")
|
|
101
103
|
|
|
102
104
|
@classmethod
|
|
103
|
-
def with_positive(cls, value: Any, *, description: str) -> GeneratedValue:
|
|
105
|
+
def with_positive(cls, value: Any, *, scenario: CoverageScenario, description: str) -> GeneratedValue:
|
|
104
106
|
return cls(
|
|
105
107
|
value=value,
|
|
106
108
|
generation_mode=GenerationMode.POSITIVE,
|
|
109
|
+
scenario=scenario,
|
|
107
110
|
description=description,
|
|
108
111
|
location=None,
|
|
109
112
|
parameter=None,
|
|
@@ -111,11 +114,12 @@ class GeneratedValue:
|
|
|
111
114
|
|
|
112
115
|
@classmethod
|
|
113
116
|
def with_negative(
|
|
114
|
-
cls, value: Any, *, description: str, location: str, parameter: str | None = None
|
|
117
|
+
cls, value: Any, *, scenario: CoverageScenario, description: str, location: str, parameter: str | None = None
|
|
115
118
|
) -> GeneratedValue:
|
|
116
119
|
return cls(
|
|
117
120
|
value=value,
|
|
118
121
|
generation_mode=GenerationMode.NEGATIVE,
|
|
122
|
+
scenario=scenario,
|
|
119
123
|
description=description,
|
|
120
124
|
location=location,
|
|
121
125
|
parameter=parameter,
|
|
@@ -449,15 +453,15 @@ def _cover_positive_for_type(
|
|
|
449
453
|
yield from cover_schema_iter(ctx, canonical)
|
|
450
454
|
if enum is not NOT_SET:
|
|
451
455
|
for value in enum:
|
|
452
|
-
yield PositiveValue(value, description="Enum value")
|
|
456
|
+
yield PositiveValue(value, scenario=CoverageScenario.ENUM_VALUE, description="Enum value")
|
|
453
457
|
elif const is not NOT_SET:
|
|
454
|
-
yield PositiveValue(const, description="Const value")
|
|
458
|
+
yield PositiveValue(const, scenario=CoverageScenario.CONST_VALUE, description="Const value")
|
|
455
459
|
elif ty is not None:
|
|
456
460
|
if ty == "null":
|
|
457
|
-
yield PositiveValue(None, description="Value null value")
|
|
461
|
+
yield PositiveValue(None, scenario=CoverageScenario.NULL_VALUE, description="Value null value")
|
|
458
462
|
elif ty == "boolean":
|
|
459
|
-
yield PositiveValue(True, description="Valid boolean value")
|
|
460
|
-
yield PositiveValue(False, description="Valid boolean value")
|
|
463
|
+
yield PositiveValue(True, scenario=CoverageScenario.VALID_BOOLEAN, description="Valid boolean value")
|
|
464
|
+
yield PositiveValue(False, scenario=CoverageScenario.VALID_BOOLEAN, description="Valid boolean value")
|
|
461
465
|
elif ty == "string":
|
|
462
466
|
yield from _positive_string(ctx, schema)
|
|
463
467
|
elif ty == "integer" or ty == "number":
|
|
@@ -563,15 +567,32 @@ def cover_schema_iter(
|
|
|
563
567
|
elif key == "maximum":
|
|
564
568
|
next = value + 1
|
|
565
569
|
if seen.insert(next):
|
|
566
|
-
yield NegativeValue(
|
|
570
|
+
yield NegativeValue(
|
|
571
|
+
next,
|
|
572
|
+
scenario=CoverageScenario.VALUE_ABOVE_MAXIMUM,
|
|
573
|
+
description="Value greater than maximum",
|
|
574
|
+
location=ctx.current_path,
|
|
575
|
+
)
|
|
567
576
|
elif key == "minimum":
|
|
568
577
|
next = value - 1
|
|
569
578
|
if seen.insert(next):
|
|
570
|
-
yield NegativeValue(
|
|
579
|
+
yield NegativeValue(
|
|
580
|
+
next,
|
|
581
|
+
scenario=CoverageScenario.VALUE_BELOW_MINIMUM,
|
|
582
|
+
description="Value smaller than minimum",
|
|
583
|
+
location=ctx.current_path,
|
|
584
|
+
)
|
|
571
585
|
elif key == "exclusiveMaximum" or key == "exclusiveMinimum" and seen.insert(value):
|
|
572
586
|
verb = "greater" if key == "exclusiveMaximum" else "smaller"
|
|
573
587
|
limit = "maximum" if key == "exclusiveMaximum" else "minimum"
|
|
574
|
-
|
|
588
|
+
scenario = (
|
|
589
|
+
CoverageScenario.VALUE_ABOVE_MAXIMUM
|
|
590
|
+
if key == "exclusiveMaximum"
|
|
591
|
+
else CoverageScenario.VALUE_BELOW_MINIMUM
|
|
592
|
+
)
|
|
593
|
+
yield NegativeValue(
|
|
594
|
+
value, scenario=scenario, description=f"Value {verb} than {limit}", location=ctx.current_path
|
|
595
|
+
)
|
|
575
596
|
elif key == "multipleOf":
|
|
576
597
|
for value_ in _negative_multiple_of(ctx, schema, value):
|
|
577
598
|
if seen.insert(value_.value):
|
|
@@ -584,7 +605,10 @@ def cover_schema_iter(
|
|
|
584
605
|
value = ""
|
|
585
606
|
if ctx.is_valid_for_location(value) and seen.insert(value):
|
|
586
607
|
yield NegativeValue(
|
|
587
|
-
value,
|
|
608
|
+
value,
|
|
609
|
+
scenario=CoverageScenario.STRING_BELOW_MIN_LENGTH,
|
|
610
|
+
description="String smaller than minLength",
|
|
611
|
+
location=ctx.current_path,
|
|
588
612
|
)
|
|
589
613
|
else:
|
|
590
614
|
with suppress(InvalidArgument):
|
|
@@ -604,7 +628,10 @@ def cover_schema_iter(
|
|
|
604
628
|
value = ctx.generate_from_schema(new_schema)
|
|
605
629
|
if ctx.is_valid_for_location(value) and seen.insert(value):
|
|
606
630
|
yield NegativeValue(
|
|
607
|
-
value,
|
|
631
|
+
value,
|
|
632
|
+
scenario=CoverageScenario.STRING_BELOW_MIN_LENGTH,
|
|
633
|
+
description="String smaller than minLength",
|
|
634
|
+
location=ctx.current_path,
|
|
608
635
|
)
|
|
609
636
|
elif key == "maxLength" and value < INTERNAL_BUFFER_SIZE:
|
|
610
637
|
try:
|
|
@@ -629,7 +656,10 @@ def cover_schema_iter(
|
|
|
629
656
|
value = ctx.generate_from_schema(new_schema)
|
|
630
657
|
if seen.insert(value):
|
|
631
658
|
yield NegativeValue(
|
|
632
|
-
value,
|
|
659
|
+
value,
|
|
660
|
+
scenario=CoverageScenario.STRING_ABOVE_MAX_LENGTH,
|
|
661
|
+
description="String larger than maxLength",
|
|
662
|
+
location=ctx.current_path,
|
|
633
663
|
)
|
|
634
664
|
except (InvalidArgument, Unsatisfiable):
|
|
635
665
|
pass
|
|
@@ -669,6 +699,7 @@ def cover_schema_iter(
|
|
|
669
699
|
if seen.insert(array_value):
|
|
670
700
|
yield NegativeValue(
|
|
671
701
|
array_value,
|
|
702
|
+
scenario=CoverageScenario.ARRAY_ABOVE_MAX_ITEMS,
|
|
672
703
|
description="Array with more items than allowed by maxItems",
|
|
673
704
|
location=ctx.current_path,
|
|
674
705
|
)
|
|
@@ -680,6 +711,7 @@ def cover_schema_iter(
|
|
|
680
711
|
if seen.insert(array_value):
|
|
681
712
|
yield NegativeValue(
|
|
682
713
|
array_value,
|
|
714
|
+
scenario=CoverageScenario.ARRAY_ABOVE_MAX_ITEMS,
|
|
683
715
|
description="Array with more items than allowed by maxItems",
|
|
684
716
|
location=ctx.current_path,
|
|
685
717
|
)
|
|
@@ -693,6 +725,7 @@ def cover_schema_iter(
|
|
|
693
725
|
if seen.insert(array_value):
|
|
694
726
|
yield NegativeValue(
|
|
695
727
|
array_value,
|
|
728
|
+
scenario=CoverageScenario.ARRAY_BELOW_MIN_ITEMS,
|
|
696
729
|
description="Array with fewer items than allowed by minItems",
|
|
697
730
|
location=ctx.current_path,
|
|
698
731
|
)
|
|
@@ -707,6 +740,7 @@ def cover_schema_iter(
|
|
|
707
740
|
template = template or ctx.generate_from_schema(_get_template_schema(schema, "object"))
|
|
708
741
|
yield NegativeValue(
|
|
709
742
|
{**template, UNKNOWN_PROPERTY_KEY: UNKNOWN_PROPERTY_VALUE},
|
|
743
|
+
scenario=CoverageScenario.OBJECT_UNEXPECTED_PROPERTIES,
|
|
710
744
|
description="Object with unexpected properties",
|
|
711
745
|
location=ctx.current_path,
|
|
712
746
|
)
|
|
@@ -838,12 +872,12 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
838
872
|
has_valid_example = False
|
|
839
873
|
if example and ctx.is_valid_for_location(example) and seen_values.insert(example):
|
|
840
874
|
has_valid_example = True
|
|
841
|
-
yield PositiveValue(example, description="Example value")
|
|
875
|
+
yield PositiveValue(example, scenario=CoverageScenario.EXAMPLE_VALUE, description="Example value")
|
|
842
876
|
if examples:
|
|
843
877
|
for example in examples:
|
|
844
878
|
if ctx.is_valid_for_location(example) and seen_values.insert(example):
|
|
845
879
|
has_valid_example = True
|
|
846
|
-
yield PositiveValue(example, description="Example value")
|
|
880
|
+
yield PositiveValue(example, scenario=CoverageScenario.EXAMPLE_VALUE, description="Example value")
|
|
847
881
|
if (
|
|
848
882
|
default
|
|
849
883
|
and not (example is not None and default == example)
|
|
@@ -852,18 +886,18 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
852
886
|
and seen_values.insert(default)
|
|
853
887
|
):
|
|
854
888
|
has_valid_example = True
|
|
855
|
-
yield PositiveValue(default, description="Default value")
|
|
889
|
+
yield PositiveValue(default, scenario=CoverageScenario.DEFAULT_VALUE, description="Default value")
|
|
856
890
|
if not has_valid_example:
|
|
857
891
|
if not min_length and not max_length or "pattern" in schema:
|
|
858
892
|
value = ctx.generate_from_schema(schema)
|
|
859
893
|
seen_values.insert(value)
|
|
860
894
|
seen_constraints.add((min_length, max_length))
|
|
861
|
-
yield PositiveValue(value, description="Valid string")
|
|
895
|
+
yield PositiveValue(value, scenario=CoverageScenario.VALID_STRING, description="Valid string")
|
|
862
896
|
elif not min_length and not max_length or "pattern" in schema:
|
|
863
897
|
value = ctx.generate_from_schema(schema)
|
|
864
898
|
seen_values.insert(value)
|
|
865
899
|
seen_constraints.add((min_length, max_length))
|
|
866
|
-
yield PositiveValue(value, description="Valid string")
|
|
900
|
+
yield PositiveValue(value, scenario=CoverageScenario.VALID_STRING, description="Valid string")
|
|
867
901
|
|
|
868
902
|
if min_length is not None and min_length < INTERNAL_BUFFER_SIZE:
|
|
869
903
|
# Exactly the minimum length
|
|
@@ -872,7 +906,9 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
872
906
|
seen_constraints.add(key)
|
|
873
907
|
value = ctx.generate_from_schema({**schema, "maxLength": min_length})
|
|
874
908
|
if seen_values.insert(value):
|
|
875
|
-
yield PositiveValue(
|
|
909
|
+
yield PositiveValue(
|
|
910
|
+
value, scenario=CoverageScenario.MINIMUM_LENGTH_STRING, description="Minimum length string"
|
|
911
|
+
)
|
|
876
912
|
|
|
877
913
|
# One character more than minimum if possible
|
|
878
914
|
larger = min_length + 1
|
|
@@ -881,7 +917,11 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
881
917
|
seen_constraints.add(key)
|
|
882
918
|
value = ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger})
|
|
883
919
|
if seen_values.insert(value):
|
|
884
|
-
yield PositiveValue(
|
|
920
|
+
yield PositiveValue(
|
|
921
|
+
value,
|
|
922
|
+
scenario=CoverageScenario.NEAR_BOUNDARY_LENGTH_STRING,
|
|
923
|
+
description="Near-boundary length string",
|
|
924
|
+
)
|
|
885
925
|
|
|
886
926
|
if max_length is not None:
|
|
887
927
|
# Exactly the maximum length
|
|
@@ -890,7 +930,9 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
890
930
|
seen_constraints.add(key)
|
|
891
931
|
value = ctx.generate_from_schema({**schema, "minLength": max_length, "maxLength": max_length})
|
|
892
932
|
if seen_values.insert(value):
|
|
893
|
-
yield PositiveValue(
|
|
933
|
+
yield PositiveValue(
|
|
934
|
+
value, scenario=CoverageScenario.MAXIMUM_LENGTH_STRING, description="Maximum length string"
|
|
935
|
+
)
|
|
894
936
|
|
|
895
937
|
# One character less than maximum if possible
|
|
896
938
|
smaller = max_length - 1
|
|
@@ -903,7 +945,11 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
903
945
|
seen_constraints.add(key)
|
|
904
946
|
value = ctx.generate_from_schema({**schema, "minLength": smaller, "maxLength": smaller})
|
|
905
947
|
if seen_values.insert(value):
|
|
906
|
-
yield PositiveValue(
|
|
948
|
+
yield PositiveValue(
|
|
949
|
+
value,
|
|
950
|
+
scenario=CoverageScenario.NEAR_BOUNDARY_LENGTH_STRING,
|
|
951
|
+
description="Near-boundary length string",
|
|
952
|
+
)
|
|
907
953
|
|
|
908
954
|
|
|
909
955
|
def closest_multiple_greater_than(y: int, x: int) -> int:
|
|
@@ -935,22 +981,22 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
935
981
|
|
|
936
982
|
if example or examples or default:
|
|
937
983
|
if example and seen.insert(example):
|
|
938
|
-
yield PositiveValue(example, description="Example value")
|
|
984
|
+
yield PositiveValue(example, scenario=CoverageScenario.EXAMPLE_VALUE, description="Example value")
|
|
939
985
|
if examples:
|
|
940
986
|
for example in examples:
|
|
941
987
|
if seen.insert(example):
|
|
942
|
-
yield PositiveValue(example, description="Example value")
|
|
988
|
+
yield PositiveValue(example, scenario=CoverageScenario.EXAMPLE_VALUE, description="Example value")
|
|
943
989
|
if (
|
|
944
990
|
default
|
|
945
991
|
and not (example is not None and default == example)
|
|
946
992
|
and not (examples is not None and any(default == ex for ex in examples))
|
|
947
993
|
and seen.insert(default)
|
|
948
994
|
):
|
|
949
|
-
yield PositiveValue(default, description="Default value")
|
|
995
|
+
yield PositiveValue(default, scenario=CoverageScenario.DEFAULT_VALUE, description="Default value")
|
|
950
996
|
elif not minimum and not maximum:
|
|
951
997
|
value = ctx.generate_from_schema(schema)
|
|
952
998
|
seen.insert(value)
|
|
953
|
-
yield PositiveValue(value, description="Valid number")
|
|
999
|
+
yield PositiveValue(value, scenario=CoverageScenario.VALID_NUMBER, description="Valid number")
|
|
954
1000
|
|
|
955
1001
|
if minimum is not None:
|
|
956
1002
|
# Exactly the minimum
|
|
@@ -959,7 +1005,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
959
1005
|
else:
|
|
960
1006
|
smallest = minimum
|
|
961
1007
|
if seen.insert(smallest):
|
|
962
|
-
yield PositiveValue(smallest, description="Minimum value")
|
|
1008
|
+
yield PositiveValue(smallest, scenario=CoverageScenario.MINIMUM_VALUE, description="Minimum value")
|
|
963
1009
|
|
|
964
1010
|
# One more than minimum if possible
|
|
965
1011
|
if multiple_of is not None:
|
|
@@ -967,7 +1013,9 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
967
1013
|
else:
|
|
968
1014
|
larger = minimum + 1
|
|
969
1015
|
if (not maximum or larger <= maximum) and seen.insert(larger):
|
|
970
|
-
yield PositiveValue(
|
|
1016
|
+
yield PositiveValue(
|
|
1017
|
+
larger, scenario=CoverageScenario.NEAR_BOUNDARY_NUMBER, description="Near-boundary number"
|
|
1018
|
+
)
|
|
971
1019
|
|
|
972
1020
|
if maximum is not None:
|
|
973
1021
|
# Exactly the maximum
|
|
@@ -976,7 +1024,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
976
1024
|
else:
|
|
977
1025
|
largest = maximum
|
|
978
1026
|
if seen.insert(largest):
|
|
979
|
-
yield PositiveValue(largest, description="Maximum value")
|
|
1027
|
+
yield PositiveValue(largest, scenario=CoverageScenario.MAXIMUM_VALUE, description="Maximum value")
|
|
980
1028
|
|
|
981
1029
|
# One less than maximum if possible
|
|
982
1030
|
if multiple_of is not None:
|
|
@@ -984,7 +1032,9 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
984
1032
|
else:
|
|
985
1033
|
smaller = maximum - 1
|
|
986
1034
|
if (minimum is None or smaller >= minimum) and seen.insert(smaller):
|
|
987
|
-
yield PositiveValue(
|
|
1035
|
+
yield PositiveValue(
|
|
1036
|
+
smaller, scenario=CoverageScenario.NEAR_BOUNDARY_NUMBER, description="Near-boundary number"
|
|
1037
|
+
)
|
|
988
1038
|
|
|
989
1039
|
|
|
990
1040
|
def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Generator[GeneratedValue, None, None]:
|
|
@@ -997,20 +1047,20 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
|
|
|
997
1047
|
|
|
998
1048
|
if example or examples or default:
|
|
999
1049
|
if example and seen.insert(example):
|
|
1000
|
-
yield PositiveValue(example, description="Example value")
|
|
1050
|
+
yield PositiveValue(example, scenario=CoverageScenario.EXAMPLE_VALUE, description="Example value")
|
|
1001
1051
|
if examples:
|
|
1002
1052
|
for example in examples:
|
|
1003
1053
|
if seen.insert(example):
|
|
1004
|
-
yield PositiveValue(example, description="Example value")
|
|
1054
|
+
yield PositiveValue(example, scenario=CoverageScenario.EXAMPLE_VALUE, description="Example value")
|
|
1005
1055
|
if (
|
|
1006
1056
|
default
|
|
1007
1057
|
and not (example is not None and default == example)
|
|
1008
1058
|
and not (examples is not None and any(default == ex for ex in examples))
|
|
1009
1059
|
and seen.insert(default)
|
|
1010
1060
|
):
|
|
1011
|
-
yield PositiveValue(default, description="Default value")
|
|
1061
|
+
yield PositiveValue(default, scenario=CoverageScenario.DEFAULT_VALUE, description="Default value")
|
|
1012
1062
|
elif seen.insert(template):
|
|
1013
|
-
yield PositiveValue(template, description="Valid array")
|
|
1063
|
+
yield PositiveValue(template, scenario=CoverageScenario.VALID_ARRAY, description="Valid array")
|
|
1014
1064
|
|
|
1015
1065
|
# Boundary and near-boundary sizes
|
|
1016
1066
|
min_items = schema.get("minItems")
|
|
@@ -1023,14 +1073,18 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
|
|
|
1023
1073
|
seen_constraints.add(larger)
|
|
1024
1074
|
value = ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger})
|
|
1025
1075
|
if seen.insert(value):
|
|
1026
|
-
yield PositiveValue(
|
|
1076
|
+
yield PositiveValue(
|
|
1077
|
+
value, scenario=CoverageScenario.NEAR_BOUNDARY_ITEMS_ARRAY, description="Near-boundary items array"
|
|
1078
|
+
)
|
|
1027
1079
|
|
|
1028
1080
|
if max_items is not None:
|
|
1029
1081
|
if max_items < INTERNAL_BUFFER_SIZE and max_items not in seen_constraints:
|
|
1030
1082
|
seen_constraints.add(max_items)
|
|
1031
1083
|
value = ctx.generate_from_schema({**schema, "minItems": max_items})
|
|
1032
1084
|
if seen.insert(value):
|
|
1033
|
-
yield PositiveValue(
|
|
1085
|
+
yield PositiveValue(
|
|
1086
|
+
value, scenario=CoverageScenario.MAXIMUM_ITEMS_ARRAY, description="Maximum items array"
|
|
1087
|
+
)
|
|
1034
1088
|
|
|
1035
1089
|
# One item smaller than maximum if possible
|
|
1036
1090
|
smaller = max_items - 1
|
|
@@ -1042,7 +1096,9 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
|
|
|
1042
1096
|
):
|
|
1043
1097
|
value = ctx.generate_from_schema({**schema, "minItems": smaller, "maxItems": smaller})
|
|
1044
1098
|
if seen.insert(value):
|
|
1045
|
-
yield PositiveValue(
|
|
1099
|
+
yield PositiveValue(
|
|
1100
|
+
value, scenario=CoverageScenario.NEAR_BOUNDARY_ITEMS_ARRAY, description="Near-boundary items array"
|
|
1101
|
+
)
|
|
1046
1102
|
|
|
1047
1103
|
if "items" in schema and "enum" in schema["items"] and isinstance(schema["items"]["enum"], list) and max_items != 0:
|
|
1048
1104
|
# Ensure there is enough items to pass `minItems` if it is specified
|
|
@@ -1050,12 +1106,20 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
|
|
|
1050
1106
|
for variant in schema["items"]["enum"]:
|
|
1051
1107
|
value = [variant] * length
|
|
1052
1108
|
if seen.insert(value):
|
|
1053
|
-
yield PositiveValue(
|
|
1109
|
+
yield PositiveValue(
|
|
1110
|
+
value,
|
|
1111
|
+
scenario=CoverageScenario.ENUM_VALUE_ITEMS_ARRAY,
|
|
1112
|
+
description="Enum value from available for items array",
|
|
1113
|
+
)
|
|
1054
1114
|
elif min_items is None and max_items is None and "items" in schema and isinstance(schema["items"], dict):
|
|
1055
1115
|
# Otherwise only an empty array is generated
|
|
1056
1116
|
sub_schema = schema["items"]
|
|
1057
1117
|
for item in cover_schema_iter(ctx, sub_schema):
|
|
1058
|
-
yield PositiveValue(
|
|
1118
|
+
yield PositiveValue(
|
|
1119
|
+
[item.value],
|
|
1120
|
+
scenario=CoverageScenario.VALID_ARRAY,
|
|
1121
|
+
description=f"Single-item array: {item.description}",
|
|
1122
|
+
)
|
|
1059
1123
|
|
|
1060
1124
|
|
|
1061
1125
|
def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Generator[GeneratedValue, None, None]:
|
|
@@ -1065,18 +1129,18 @@ def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Gene
|
|
|
1065
1129
|
|
|
1066
1130
|
if example or examples or default:
|
|
1067
1131
|
if example:
|
|
1068
|
-
yield PositiveValue(example, description="Example value")
|
|
1132
|
+
yield PositiveValue(example, scenario=CoverageScenario.EXAMPLE_VALUE, description="Example value")
|
|
1069
1133
|
if examples:
|
|
1070
1134
|
for example in examples:
|
|
1071
|
-
yield PositiveValue(example, description="Example value")
|
|
1135
|
+
yield PositiveValue(example, scenario=CoverageScenario.EXAMPLE_VALUE, description="Example value")
|
|
1072
1136
|
if (
|
|
1073
1137
|
default
|
|
1074
1138
|
and not (example is not None and default == example)
|
|
1075
1139
|
and not (examples is not None and any(default == ex for ex in examples))
|
|
1076
1140
|
):
|
|
1077
|
-
yield PositiveValue(default, description="Default value")
|
|
1141
|
+
yield PositiveValue(default, scenario=CoverageScenario.DEFAULT_VALUE, description="Default value")
|
|
1078
1142
|
else:
|
|
1079
|
-
yield PositiveValue(template, description="Valid object")
|
|
1143
|
+
yield PositiveValue(template, scenario=CoverageScenario.VALID_OBJECT, description="Valid object")
|
|
1080
1144
|
|
|
1081
1145
|
properties = schema.get("properties", {})
|
|
1082
1146
|
required = set(schema.get("required", []))
|
|
@@ -1087,22 +1151,36 @@ def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Gene
|
|
|
1087
1151
|
for name in optional:
|
|
1088
1152
|
combo = {k: v for k, v in template.items() if k in required or k == name}
|
|
1089
1153
|
if combo != template:
|
|
1090
|
-
yield PositiveValue(
|
|
1154
|
+
yield PositiveValue(
|
|
1155
|
+
combo,
|
|
1156
|
+
scenario=CoverageScenario.OBJECT_REQUIRED_AND_OPTIONAL,
|
|
1157
|
+
description=f"Object with all required properties and '{name}'",
|
|
1158
|
+
)
|
|
1091
1159
|
# Generate one combination for each size from 2 to N-1
|
|
1092
1160
|
for selection in select_combinations(optional):
|
|
1093
1161
|
combo = {k: v for k, v in template.items() if k in required or k in selection}
|
|
1094
|
-
yield PositiveValue(
|
|
1162
|
+
yield PositiveValue(
|
|
1163
|
+
combo,
|
|
1164
|
+
scenario=CoverageScenario.OBJECT_REQUIRED_AND_OPTIONAL,
|
|
1165
|
+
description="Object with all required and a subset of optional properties",
|
|
1166
|
+
)
|
|
1095
1167
|
# Generate only required properties
|
|
1096
1168
|
if set(properties) != required:
|
|
1097
1169
|
only_required = {k: v for k, v in template.items() if k in required}
|
|
1098
|
-
yield PositiveValue(
|
|
1170
|
+
yield PositiveValue(
|
|
1171
|
+
only_required,
|
|
1172
|
+
scenario=CoverageScenario.OBJECT_ONLY_REQUIRED,
|
|
1173
|
+
description="Object with only required properties",
|
|
1174
|
+
)
|
|
1099
1175
|
seen = HashSet()
|
|
1100
1176
|
for name, sub_schema in properties.items():
|
|
1101
1177
|
seen.insert(template.get(name))
|
|
1102
1178
|
for new in cover_schema_iter(ctx, sub_schema):
|
|
1103
1179
|
if seen.insert(new.value):
|
|
1104
1180
|
yield PositiveValue(
|
|
1105
|
-
{**template, name: new.value},
|
|
1181
|
+
{**template, name: new.value},
|
|
1182
|
+
scenario=CoverageScenario.VALID_OBJECT,
|
|
1183
|
+
description=f"Object with valid '{name}' value: {new.description}",
|
|
1106
1184
|
)
|
|
1107
1185
|
seen.clear()
|
|
1108
1186
|
|
|
@@ -1126,6 +1204,7 @@ def _negative_enum(ctx: CoverageContext, value: list, seen: HashSet) -> Generato
|
|
|
1126
1204
|
).filter(is_not_in_value)
|
|
1127
1205
|
yield NegativeValue(
|
|
1128
1206
|
ctx.generate_from(strategy),
|
|
1207
|
+
scenario=CoverageScenario.INVALID_ENUM_VALUE,
|
|
1129
1208
|
description="Invalid enum value",
|
|
1130
1209
|
location=ctx.current_path,
|
|
1131
1210
|
)
|
|
@@ -1140,6 +1219,7 @@ def _negative_properties(
|
|
|
1140
1219
|
for value in cover_schema_iter(nctx, sub_schema):
|
|
1141
1220
|
yield NegativeValue(
|
|
1142
1221
|
{**template, key: value.value},
|
|
1222
|
+
scenario=value.scenario,
|
|
1143
1223
|
description=f"Object with invalid '{key}' value: {value.description}",
|
|
1144
1224
|
location=nctx.current_path,
|
|
1145
1225
|
parameter=key,
|
|
@@ -1159,6 +1239,7 @@ def _negative_pattern_properties(
|
|
|
1159
1239
|
for value in cover_schema_iter(nctx, sub_schema):
|
|
1160
1240
|
yield NegativeValue(
|
|
1161
1241
|
{**template, key: value.value},
|
|
1242
|
+
scenario=value.scenario,
|
|
1162
1243
|
description=f"Object with invalid pattern key '{key}' ('{pattern}') value: {value.description}",
|
|
1163
1244
|
location=nctx.current_path,
|
|
1164
1245
|
)
|
|
@@ -1172,6 +1253,7 @@ def _negative_items(ctx: CoverageContext, schema: dict[str, Any] | bool) -> Gene
|
|
|
1172
1253
|
if ctx.leads_to_negative_test_case(items):
|
|
1173
1254
|
yield NegativeValue(
|
|
1174
1255
|
items,
|
|
1256
|
+
scenario=value.scenario,
|
|
1175
1257
|
description=f"Array with invalid items: {value.description}",
|
|
1176
1258
|
location=nctx.current_path,
|
|
1177
1259
|
)
|
|
@@ -1194,6 +1276,7 @@ def _negative_pattern(
|
|
|
1194
1276
|
.filter(partial(_not_matching_pattern, pattern=compiled))
|
|
1195
1277
|
.filter(ctx.is_valid_for_location)
|
|
1196
1278
|
),
|
|
1279
|
+
scenario=CoverageScenario.INVALID_PATTERN,
|
|
1197
1280
|
description=f"Value not matching the '{pattern}' pattern",
|
|
1198
1281
|
location=ctx.current_path,
|
|
1199
1282
|
)
|
|
@@ -1208,6 +1291,7 @@ def _negative_multiple_of(
|
|
|
1208
1291
|
) -> Generator[GeneratedValue, None, None]:
|
|
1209
1292
|
yield NegativeValue(
|
|
1210
1293
|
ctx.generate_from_schema(_with_negated_key(schema, "multipleOf", multiple_of)),
|
|
1294
|
+
scenario=CoverageScenario.NOT_MULTIPLE_OF,
|
|
1211
1295
|
description=f"Non-multiple of {multiple_of}",
|
|
1212
1296
|
location=ctx.current_path,
|
|
1213
1297
|
)
|
|
@@ -1215,7 +1299,12 @@ def _negative_multiple_of(
|
|
|
1215
1299
|
|
|
1216
1300
|
def _negative_unique_items(ctx: CoverageContext, schema: dict) -> Generator[GeneratedValue, None, None]:
|
|
1217
1301
|
unique = jsonify(ctx.generate_from_schema({**schema, "type": "array", "minItems": 1, "maxItems": 1}))
|
|
1218
|
-
yield NegativeValue(
|
|
1302
|
+
yield NegativeValue(
|
|
1303
|
+
unique + unique,
|
|
1304
|
+
scenario=CoverageScenario.NON_UNIQUE_ITEMS,
|
|
1305
|
+
description="Non-unique items",
|
|
1306
|
+
location=ctx.current_path,
|
|
1307
|
+
)
|
|
1219
1308
|
|
|
1220
1309
|
|
|
1221
1310
|
def _negative_required(
|
|
@@ -1224,6 +1313,7 @@ def _negative_required(
|
|
|
1224
1313
|
for key in required:
|
|
1225
1314
|
yield NegativeValue(
|
|
1226
1315
|
{k: v for k, v in template.items() if k != key},
|
|
1316
|
+
scenario=CoverageScenario.OBJECT_MISSING_REQUIRED_PROPERTY,
|
|
1227
1317
|
description=f"Missing required property: {key}",
|
|
1228
1318
|
location=ctx.current_path,
|
|
1229
1319
|
parameter=key,
|
|
@@ -1253,6 +1343,7 @@ def _negative_format(ctx: CoverageContext, schema: dict, format: str) -> Generat
|
|
|
1253
1343
|
strategy = strategy.filter(functools.partial(_is_invalid_format, format=format))
|
|
1254
1344
|
yield NegativeValue(
|
|
1255
1345
|
ctx.generate_from(strategy),
|
|
1346
|
+
scenario=CoverageScenario.INVALID_FORMAT,
|
|
1256
1347
|
description=f"Value not matching the '{format}' format",
|
|
1257
1348
|
location=ctx.current_path,
|
|
1258
1349
|
)
|
|
@@ -1379,7 +1470,9 @@ def _negative_type(
|
|
|
1379
1470
|
for strategy in strategies.values():
|
|
1380
1471
|
value = ctx.generate_from(strategy)
|
|
1381
1472
|
if seen.insert(value) and ctx.is_valid_for_location(value):
|
|
1382
|
-
yield NegativeValue(
|
|
1473
|
+
yield NegativeValue(
|
|
1474
|
+
value, scenario=CoverageScenario.INCORRECT_TYPE, description="Incorrect type", location=ctx.current_path
|
|
1475
|
+
)
|
|
1383
1476
|
|
|
1384
1477
|
|
|
1385
1478
|
def push_examples_to_properties(schema: dict[str, Any]) -> None:
|