From a75fb169f460b827173d9c96e06b14ca71e0d9bd Mon Sep 17 00:00:00 2001 From: Dmitry Dygalo Date: Wed, 28 Feb 2024 09:04:36 +0100 Subject: [PATCH] fix: not respecting allow_x00 and codec arguments for values in some schemas Signed-off-by: Dmitry Dygalo --- CHANGELOG.md | 2 ++ src/hypothesis_jsonschema/_from_schema.py | 44 +++++++++++++++++------ tests/test_from_schema.py | 24 +++++++++++++ 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f989fb5..d4ea6ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +- Fix not respecting `allow_x00` and `codec` arguments for values in some schemas + #### 0.23.0 - 2023-09-24 - Add new `allow_x00=` and `codec=` arguments to `from_schema()`, so that you can control generated strings more precisely. diff --git a/src/hypothesis_jsonschema/_from_schema.py b/src/hypothesis_jsonschema/_from_schema.py index e34b264..7ab9036 100644 --- a/src/hypothesis_jsonschema/_from_schema.py +++ b/src/hypothesis_jsonschema/_from_schema.py @@ -79,11 +79,16 @@ def from_js_regex(pattern: str, alphabet: CharStrategy) -> st.SearchStrategy[str def merged_as_strategies( - schemas: List[Schema], custom_formats: Optional[Dict[str, st.SearchStrategy[str]]] + schemas: List[Schema], + *, + alphabet: CharStrategy, + custom_formats: Optional[Dict[str, st.SearchStrategy[str]]], ) -> st.SearchStrategy[JSONType]: assert schemas, "internal error: must pass at least one schema to merge" if len(schemas) == 1: - return from_schema(schemas[0], custom_formats=custom_formats) + return __from_schema( + schemas[0], alphabet=alphabet, custom_formats=custom_formats + ) # Try to merge combinations of strategies. strats = [] combined: Set[str] = set() @@ -96,7 +101,9 @@ def merged_as_strategies( s = merged([inputs[g] for g in group]) if s is not None and s != FALSEY: strats.append( - from_schema(s, custom_formats=custom_formats).filter( + __from_schema( + s, alphabet=alphabet, custom_formats=custom_formats + ).filter( lambda obj, validators=tuple( make_validator(s).is_valid for s in schemas ): all(v(obj) for v in validators) @@ -165,7 +172,7 @@ def __from_schema( schema: Union[bool, Schema], *, alphabet: CharStrategy, - custom_formats: Optional[Dict[str, st.SearchStrategy[str]]] = None, + custom_formats: Optional[Dict[str, st.SearchStrategy[str]]], ) -> st.SearchStrategy[JSONType]: try: schema = resolve_all_refs(schema) @@ -217,19 +224,28 @@ def __from_schema( not_ = schema.pop("not") assert isinstance(not_, dict) validator = make_validator(not_).is_valid - return from_schema(schema, custom_formats=custom_formats).filter( - lambda v: not validator(v) - ) + return __from_schema( + schema, alphabet=alphabet, custom_formats=custom_formats + ).filter(lambda v: not validator(v)) if "anyOf" in schema: tmp = schema.copy() ao = tmp.pop("anyOf") assert isinstance(ao, list) - return st.one_of([merged_as_strategies([tmp, s], custom_formats) for s in ao]) + return st.one_of( + [ + merged_as_strategies( + [tmp, s], alphabet=alphabet, custom_formats=custom_formats + ) + for s in ao + ] + ) if "allOf" in schema: tmp = schema.copy() ao = tmp.pop("allOf") assert isinstance(ao, list) - return merged_as_strategies([tmp, *ao], custom_formats) + return merged_as_strategies( + [tmp, *ao], alphabet=alphabet, custom_formats=custom_formats + ) if "oneOf" in schema: tmp = schema.copy() oo = tmp.pop("oneOf") @@ -237,7 +253,7 @@ def __from_schema( schemas = [merged([tmp, s]) for s in oo] return st.one_of( [ - from_schema(s, custom_formats=custom_formats) + __from_schema(s, alphabet=alphabet, custom_formats=custom_formats) for s in schemas if s is not None ] @@ -692,7 +708,13 @@ def from_object_schema(draw: Any) -> Any: pattern_schemas.insert(0, properties[key]) if pattern_schemas: - out[key] = draw(merged_as_strategies(pattern_schemas, custom_formats)) + out[key] = draw( + merged_as_strategies( + pattern_schemas, + alphabet=alphabet, + custom_formats=custom_formats, + ) + ) else: out[key] = draw( __from_schema( diff --git a/tests/test_from_schema.py b/tests/test_from_schema.py index 8d111e7..30e0415 100644 --- a/tests/test_from_schema.py +++ b/tests/test_from_schema.py @@ -576,3 +576,27 @@ def test_errors_on_unencodable_property_name(data): data.draw(from_schema(non_ascii_schema, codec=None)) with pytest.raises(InvalidArgument, match=r"'é' cannot be encoded as 'ascii'"): data.draw(from_schema(non_ascii_schema, codec="ascii")) + + +@settings(deadline=None) +@given(data=st.data()) +def test_no_null_bytes(data): + schema = { + "type": "object", + "properties": { + "p1": {"type": "string"}, + "p2": { + "type": "object", + "properties": {"pp1": {"type": "string"}}, + "required": ["pp1"], + "additionalProperties": False, + }, + "p3": {"type": "array", "items": {"type": "string"}}, + }, + "required": ["p1", "p2", "p3"], + "additionalProperties": False, + } + example = data.draw(from_schema(schema, allow_x00=False)) + assert "\x00" not in example["p1"] + assert "\x00" not in example["p2"]["pp1"] + assert all("\x00" not in item for item in example["p3"])