diff --git a/tests/data/malformed/bad.graphql b/tests/data/malformed/bad.graphql new file mode 100644 index 000000000..eb7c2a316 --- /dev/null +++ b/tests/data/malformed/bad.graphql @@ -0,0 +1,3 @@ +type Query { + user: +} diff --git a/tests/data/malformed/bad_openapi.yaml b/tests/data/malformed/bad_openapi.yaml new file mode 100644 index 000000000..2815edcd1 --- /dev/null +++ b/tests/data/malformed/bad_openapi.yaml @@ -0,0 +1,4 @@ +openapi: 3.0.0 +info: + title: Bad +paths: [ diff --git a/tests/data/malformed/dangling_local_ref.json b/tests/data/malformed/dangling_local_ref.json new file mode 100644 index 000000000..af00a507f --- /dev/null +++ b/tests/data/malformed/dangling_local_ref.json @@ -0,0 +1 @@ +{"$ref": "#/definitions/Missing"} diff --git a/tests/data/malformed/empty_jsonschema.json b/tests/data/malformed/empty_jsonschema.json new file mode 100644 index 000000000..e69de29bb diff --git a/tests/data/malformed/enum_as_dict.json b/tests/data/malformed/enum_as_dict.json new file mode 100644 index 000000000..a54f9fc9a --- /dev/null +++ b/tests/data/malformed/enum_as_dict.json @@ -0,0 +1,4 @@ +{ + "type": "string", + "enum": {"one": 1} +} diff --git a/tests/data/malformed/missing_external_ref.json b/tests/data/malformed/missing_external_ref.json new file mode 100644 index 000000000..ac5849522 --- /dev/null +++ b/tests/data/malformed/missing_external_ref.json @@ -0,0 +1 @@ +{"$ref": "missing.json"} diff --git a/tests/data/malformed/non_dict_root.yaml b/tests/data/malformed/non_dict_root.yaml new file mode 100644 index 000000000..452daddcf --- /dev/null +++ b/tests/data/malformed/non_dict_root.yaml @@ -0,0 +1,3 @@ +- not +- a +- mapping diff --git a/tests/data/malformed/pointer_through_scalar.json b/tests/data/malformed/pointer_through_scalar.json new file mode 100644 index 000000000..f9f3c090f --- /dev/null +++ b/tests/data/malformed/pointer_through_scalar.json @@ -0,0 +1,6 @@ +{ + "definitions": { + "Name": "scalar" + }, + "$ref": "#/definitions/Name/properties/value" +} diff --git a/tests/data/malformed/pointer_through_scalar_openapi.yaml b/tests/data/malformed/pointer_through_scalar_openapi.yaml new file mode 100644 index 000000000..4d8dd17bb --- /dev/null +++ b/tests/data/malformed/pointer_through_scalar_openapi.yaml @@ -0,0 +1,10 @@ +openapi: 3.0.0 +info: + title: Bad + version: '1.0' +paths: {} +components: + schemas: + Name: scalar + UseName: + $ref: '#/components/schemas/Name/properties/value' diff --git a/tests/data/malformed/required_as_string.json b/tests/data/malformed/required_as_string.json new file mode 100644 index 000000000..80f939b64 --- /dev/null +++ b/tests/data/malformed/required_as_string.json @@ -0,0 +1,7 @@ +{ + "type": "object", + "required": "name", + "properties": { + "name": {"type": "string"} + } +} diff --git a/tests/data/malformed/truncated_jsonschema.json b/tests/data/malformed/truncated_jsonschema.json new file mode 100644 index 000000000..b383686ba --- /dev/null +++ b/tests/data/malformed/truncated_jsonschema.json @@ -0,0 +1 @@ +{"type": "object", "properties": { diff --git a/tests/data/malformed/wrong_type_properties.json b/tests/data/malformed/wrong_type_properties.json new file mode 100644 index 000000000..9cabff28f --- /dev/null +++ b/tests/data/malformed/wrong_type_properties.json @@ -0,0 +1,4 @@ +{ + "type": "object", + "properties": [] +} diff --git a/tests/main/test_error_messages.py b/tests/main/test_error_messages.py new file mode 100644 index 000000000..494f74929 --- /dev/null +++ b/tests/main/test_error_messages.py @@ -0,0 +1,110 @@ +"""Regression tests for malformed CLI input diagnostics.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from datamodel_code_generator.__main__ import Exit +from tests.main.conftest import DATA_PATH, InputFileTypeLiteral, run_main_and_assert + +if TYPE_CHECKING: + from pathlib import Path + + +MALFORMED_DATA_PATH = DATA_PATH / "malformed" +TRACEBACK_HEADER = "Traceback (most recent call last)" + +ERROR_CASES: tuple[tuple[str, InputFileTypeLiteral, str], ...] = ( + ("truncated_jsonschema.json", "jsonschema", TRACEBACK_HEADER), + ("bad_openapi.yaml", "openapi", TRACEBACK_HEADER), + ("bad.graphql", "graphql", TRACEBACK_HEADER), + ("non_dict_root.yaml", "openapi", TRACEBACK_HEADER), + ("pointer_through_scalar_openapi.yaml", "openapi", TRACEBACK_HEADER), + ( + "pointer_through_scalar.json", + "jsonschema", + "Error at schema path 'pointer_through_scalar.json/#/definitions/Name': ValidationError", + ), + ("wrong_type_properties.json", "jsonschema", "Error at schema path 'wrong_type_properties.json'"), + ("required_as_string.json", "jsonschema", "Error at schema path 'required_as_string.json'"), + ("enum_as_dict.json", "jsonschema", "Error at schema path 'enum_as_dict.json'"), + ("missing_external_ref.json", "jsonschema", "$ref file not found:"), + ("empty_jsonschema.json", "jsonschema", "Models not found in the input data"), +) + +MISSING_INPUT_CASES: tuple[tuple[InputFileTypeLiteral | None, str], ...] = ( + (None, "File not found"), + # Explicit input types currently bypass auto-detection's missing-file check. + # Keep this pinned until the diagnostic behavior is intentionally changed. + ("jsonschema", TRACEBACK_HEADER), +) + +TOLERATED_CASES: tuple[tuple[str, InputFileTypeLiteral], ...] = (("dangling_local_ref.json", "jsonschema"),) + + +@pytest.mark.parametrize( + ("fixture_name", "input_file_type", "expected_stderr_contains"), + ERROR_CASES, + ids=[fixture_name for fixture_name, _, _ in ERROR_CASES], +) +def test_malformed_input_error_messages( + fixture_name: str, + input_file_type: InputFileTypeLiteral, + expected_stderr_contains: str, + output_file: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + """Malformed inputs keep their current stderr signal and non-zero exit code.""" + run_main_and_assert( + input_path=MALFORMED_DATA_PATH / fixture_name, + output_path=output_file, + input_file_type=input_file_type, + expected_exit=Exit.ERROR, + capsys=capsys, + expected_stderr_contains=expected_stderr_contains, + output_should_not_exist=True, + ) + + +@pytest.mark.parametrize( + ("input_file_type", "expected_stderr_contains"), + MISSING_INPUT_CASES, + ids=[input_file_type or "auto" for input_file_type, _ in MISSING_INPUT_CASES], +) +def test_missing_input_error_messages( + input_file_type: InputFileTypeLiteral | None, + expected_stderr_contains: str, + output_file: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + """Missing local input paths keep their current CLI diagnostics.""" + run_main_and_assert( + input_path=MALFORMED_DATA_PATH / "missing.json", + output_path=output_file, + input_file_type=input_file_type, + expected_exit=Exit.ERROR, + capsys=capsys, + expected_stderr_contains=expected_stderr_contains, + output_should_not_exist=True, + ) + + +@pytest.mark.parametrize( + ("fixture_name", "input_file_type"), + TOLERATED_CASES, + ids=[fixture_name for fixture_name, _ in TOLERATED_CASES], +) +def test_malformed_input_tolerated_behavior( + fixture_name: str, + input_file_type: InputFileTypeLiteral, + output_file: Path, +) -> None: + """Document malformed inputs that are intentionally tolerated until stricter diagnostics exist.""" + run_main_and_assert( + input_path=MALFORMED_DATA_PATH / fixture_name, + output_path=output_file, + input_file_type=input_file_type, + expected_exit=Exit.OK, + )