test_json_schema - #452
Conversation
| def _format_loader(data): | ||
| if isinstance(data, str): | ||
| with suppress(ValueError): | ||
| return JSONSchemaBuiltinFormat(data) | ||
| return data |
There was a problem hiding this comment.
Why custom loader is required here? Why data for unkown types are directly returned?
There was a problem hiding this comment.
Custom loader needed because format: JSONSchemaBuiltinFormat | str is ambiguous for the default union loader (both sides are string-shaped, so plain str would always win and the enum would never be produced) - enum is tried first, falling back to the raw string for custom formats (see test at test_json_schema_facade.py, test test_load_json_schema_format_uses_builtin_enum_or_falls_back_to_str with "format": "date-time"). Good catch on the fallthrough for non-str data - fixed to raise TypeLoadError.
| def _append_with_patcher(self: S, patcher: Callable[[JSONSchema], JSONSchema]) -> S: | ||
| self_copy = copy(self) | ||
| self_copy = deepcopy(self) |
There was a problem hiding this comment.
Why copy patcher objects? They should be immutable
There was a problem hiding this comment.
The actual issue was list aliasing on _patchers after the shallow copy(). It was same for previous and new item. Use list for fix it, not append
| ) | ||
| from adaptix.load_error import AggregateLoadError | ||
|
|
||
| DIALECT_URI = "https://json-schema.org/draft/2020-12/schema" |
There was a problem hiding this comment.
This constant exists in main library code
| def assert_structure( | ||
| actual: Union[Mapping[str, Any], list], | ||
| expected: Union[Mapping[str, Any], list], | ||
| *, | ||
| path: str = "", | ||
| strict: bool = False, | ||
| ) -> None: |
There was a problem hiding this comment.
Tests already have dirty-equals pacakge in dependencies
There was a problem hiding this comment.
Thanks!
dirty_equals doesn't have exactly what assert_structure needs — no recursive/deep-partial mode, so a nested dict has to be wrapped in its own IsPartialDict at every level, it doesn't happen automatically. But building on top of it still simplified things a lot: assert_structure no longer implements any comparison logic itself, it just builds a tree of IsPartialDict/IsList from the plain dict/list literal and lets dirty_equals do the actual matching.
There was a problem hiding this comment.
i remove assert_structure to check all with assert_morphing and, where i can't do it - write raw asserts on keys
| @pytest.mark.parametrize( | ||
| ["raw", "expected"], | ||
| [ | ||
| ("null", JSONSchemaType.NULL), | ||
| ("boolean", JSONSchemaType.BOOLEAN), | ||
| ("object", JSONSchemaType.OBJECT), | ||
| ("array", JSONSchemaType.ARRAY), | ||
| ("number", JSONSchemaType.NUMBER), | ||
| ("integer", JSONSchemaType.INTEGER), | ||
| ("string", JSONSchemaType.STRING), | ||
| ], | ||
| ) | ||
| def test_load_json_schema_type_enum_values(raw: str, expected: JSONSchemaType): | ||
| schema = load_json_schema({"type": raw}) | ||
|
|
||
| assert schema.type == expected | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ["schema_data", "expected"], | ||
| [ | ||
| ( | ||
| {"type": "string", "title": "MyStr", "description": "A string"}, | ||
| JSONSchema(type=JSONSchemaType.STRING, title="MyStr", description="A string"), | ||
| ), | ||
| ( | ||
| {"type": "string", "format": "date-time"}, | ||
| JSONSchema(type=JSONSchemaType.STRING, format=JSONSchemaBuiltinFormat.DATE_TIME), | ||
| ), | ||
| ( | ||
| {"type": "string", "format": "my-custom-format"}, | ||
| JSONSchema(type=JSONSchemaType.STRING, format="my-custom-format"), | ||
| ), | ||
| ( | ||
| { | ||
| "type": "object", | ||
| "properties": { | ||
| "name": {"type": "string"}, | ||
| "age": {"type": "integer"}, | ||
| }, | ||
| "required": ["name"], | ||
| }, | ||
| JSONSchema( | ||
| type=JSONSchemaType.OBJECT, | ||
| properties={ | ||
| "name": JSONSchema(type=JSONSchemaType.STRING), | ||
| "age": JSONSchema(type=JSONSchemaType.INTEGER), | ||
| }, | ||
| required=["name"], | ||
| ), | ||
| ), | ||
| ( | ||
| {"anyOf": [{"type": "string"}, {"type": "integer"}]}, | ||
| JSONSchema(any_of=[ | ||
| JSONSchema(type=JSONSchemaType.STRING), | ||
| JSONSchema(type=JSONSchemaType.INTEGER), | ||
| ]), | ||
| ), | ||
| ], | ||
| ) | ||
| def test_load_json_schema_parses_dict_to_object(schema_data, expected): | ||
| schema = load_json_schema(schema_data) | ||
|
|
||
| assert schema == expected | ||
|
|
||
|
|
||
| def test_load_json_schema_strict_unknown_field_raises(): | ||
| with pytest.raises(AggregateLoadError): | ||
| load_json_schema({"type": "string", "x-custom": "value"}, error_on_extra=True) | ||
|
|
||
|
|
||
| def test_load_json_schema_lax_unknown_field_goes_to_extra_keywords(): | ||
| schema = load_json_schema({"type": "string", "x-custom": "value"}, error_on_extra=False) | ||
|
|
||
| assert schema.extra_keywords == {"x-custom": "value"} | ||
|
|
||
|
|
||
| def test_load_json_schema_empty_dict(): | ||
| schema = load_json_schema({}) | ||
|
|
||
| assert schema == JSONSchema() |
There was a problem hiding this comment.
What are these tests for? They check Adaptix's basic model loading functionality
There was a problem hiding this comment.
Fair point - trimmed the tests that only exercised generic Adaptix dataclass/enum loading, since that's already covered by core Adaptix tests.
Kept only what's actually specific to this facade's retort config in func.py: the custom _format_loader and the error_on_extra behavior (ExtraForbid vs extra_out="extra_keywords") - that logic is unique to load_json_schema and isn't tested elsewhere.
| @pytest.mark.parametrize( | ||
| ["with_dialect_uri", "expected_schema"], | ||
| [ | ||
| (True, DIALECT_URI), | ||
| (False, NOT_EXISTS), | ||
| ], | ||
| ids=["with_uri", "without_uri"], | ||
| ) | ||
| def test_generate_json_schema_dialect_uri(with_dialect_uri: bool, expected_schema: Any): # noqa: FBT001 | ||
| schema = generate_json_schema(Retort(), SimpleModel, Direction.INPUT, with_dialect_uri=with_dialect_uri) | ||
|
|
||
| assert_structure(schema, {"$schema": expected_schema}) | ||
|
|
||
|
|
||
| def test_generate_json_schema_top_level_keys(): | ||
| schema = generate_json_schema(Retort(), SimpleModel, Direction.INPUT) | ||
|
|
||
| assert_structure(schema, { | ||
| "$schema": EXISTS, | ||
| "$ref": EXISTS, | ||
| "$defs": { | ||
| "SimpleModel": EXISTS, | ||
| }, | ||
| }, strict=True) |
There was a problem hiding this comment.
Why these tests checks only keys?
There was a problem hiding this comment.
Switched this one to assert_morphing. test_generate_json_schema_dialect_uri just check $schema - remove assert_structure from file
| def make_src(tp, schema=None): | ||
| if schema is None: | ||
| schema = JSONSchema() | ||
| return LocalRefSource(value=None, json_schema=schema, loc_stack=LocStack(TypeHintLoc(tp))) | ||
|
|
||
|
|
||
| def test_traverse_flat_schema_yields_itself(): | ||
| schema = JSONSchema(title="flat") | ||
|
|
||
| result = list(traverse_json_schema(schema)) | ||
|
|
||
| assert result == [schema] |
There was a problem hiding this comment.
Why these tests are exist? I do not think that tests for such internal heplers could be useful. This logic shuld be covered via tests of actual user functionality
There was a problem hiding this comment.
I remove few of tests from that file
-
test_traverse_flat_schema_yields_itself (traversal returns itself for a schema with no nested fields) -> covered by test_generate_json_schema_matches_full_expected_shape: the name/value fields there are flat schemas, and them showing up correctly in the final properties proves the base case works.
-
test_traverse_ref_to_local_source_visits_referenced_schema (traversal follows into a LocalRefSource) -> covered by test_generate_schemas_namespace_deduplicates_shared_types: Tag is nested in both Article and Post via a real LocalRefSource, and it correctly dedupes into a single $defs entry only if traversal actually follows the ref.
-
test_replace_local_ref_with_prefix_and_ctx (replace_json_schema_ref substitutes $ref from ctx with a prefix) -> covered by test_json_schema_pinned_ref: a custom ref name (ref="MyString") ends up as "$ref": "#/$defs/MyString" in the generated schema, exercising the same ctx→prefix mechanics through the public API.
-
test_replace_nested_properties_recursively_resolved (replace recurses into nested properties) -> covered by the new test_generate_json_schema_prefix_items_dedupes_shared_ref (and any test with a nested dataclass field, e.g. Article.tag): a $ref inside a nested field resolves correctly in the final schema, which requires recursing through properties/prefixItems.
| def apply_patch(patch: JSONSchemaPatch, schema: JSONSchema) -> JSONSchema: | ||
| for patcher in patch.get_patchers(): | ||
| schema = patcher(schema) | ||
| return schema | ||
|
|
||
|
|
||
| def test_replace_sets_field(): | ||
| schema = JSONSchema(title="old") | ||
|
|
||
| result = apply_patch(JSONSchemaPatch().replace("title", lambda _: "new"), schema) | ||
|
|
||
| assert result.title == "new" |
There was a problem hiding this comment.
Such tests should use high level API (user API) of retorts
There was a problem hiding this comment.
Removed 3 tests from here:
-
test_replace_sets_field -> test_patch_replace_title (test_json_schema_overrides.py): same .replace("title", ...), same outcome — the new value ends up in the schema, just observed through a real retort instead of manual apply_patch.
-
test_merge_with_chain_first_override_wins -> test_patch_merge_with_chain_first_overrides_inferred: Chain.FIRST overrides the inferred type on price - override wins on a conflicting field, same property being tested.
-
test_merge_with_chain_last_base_wins -> test_patch_merge_with_chain_last_base_wins_over_conflicting_override sets type on both sides - base has the inferred "string", the override supplies "integer" — and asserts the generated schema keeps "string". That conflict resolved by Chain.LAST. With a real conflict on both sides, base wins.
|
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||



No description provided.