Skip to content

Commit

Permalink
chore: Improve error messages for JSON pointer resolution errors
Browse files Browse the repository at this point in the history
Ref: #1472
  • Loading branch information
Stranger6667 committed Oct 13, 2023
1 parent 080ec4e commit 00a707d
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 10 deletions.
3 changes: 2 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Changelog
- Add ``case`` as the first argument to ``AuthContext.set``. Previous calling convention is still supported. `#1788`_
- Disable the 'explain' phase in Hypothesis to improve performance. `#1808`_
- Do not display ``InsecureRequestWarning`` in CLI output if the user explicitly provided ``--request-tls-verify=false``. `#1780`_
- Enhance CLI output for schema loading and internal errors, providing clearer diagnostics and guidance. `#1781`_, `#1517`_
- Enhance CLI output for schema loading and internal errors, providing clearer diagnostics and guidance. `#1781`_, `#1517`_, `#1472`_

Before:

Expand Down Expand Up @@ -3498,6 +3498,7 @@ Deprecated
.. _#1517: https://github.com/schemathesis/schemathesis/issues/1517
.. _#1514: https://github.com/schemathesis/schemathesis/issues/1514
.. _#1485: https://github.com/schemathesis/schemathesis/issues/1485
.. _#1472: https://github.com/schemathesis/schemathesis/issues/1472
.. _#1464: https://github.com/schemathesis/schemathesis/issues/1464
.. _#1463: https://github.com/schemathesis/schemathesis/issues/1463
.. _#1452: https://github.com/schemathesis/schemathesis/issues/1452
Expand Down
2 changes: 1 addition & 1 deletion src/schemathesis/cli/output/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def display_errors(context: ExecutionContext, event: events.Finished) -> None:
"Add this option to your command line parameters to see full tracebacks: --show-errors-tracebacks", fg="red"
)
click.secho(
f"\nNeed more help?\n" f" Join our Discord server: {DISCORD_LINK}\n",
f"\nNeed more help?\n" f" Join our Discord server: {DISCORD_LINK}",
fg="red",
)

Expand Down
22 changes: 18 additions & 4 deletions src/schemathesis/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, NoReturn, Optional, Tuple, Type, Union

import hypothesis.errors
from jsonschema import ValidationError
from jsonschema import RefResolutionError, ValidationError

from .constants import SERIALIZERS_SUGGESTION_MESSAGE
from .failures import FailureContext
Expand Down Expand Up @@ -140,6 +140,9 @@ def get_timeout_error(deadline: Union[float, int]) -> Type[CheckFailed]:
return _get_hashed_exception("TimeoutError", str(deadline))


SCHEMA_ERROR_SUGGESTION = "Ensure that the definition complies with the OpenAPI specification"


@dataclass
class OperationSchemaError(Exception):
"""Schema associated with an API operation contains an error."""
Expand All @@ -149,7 +152,6 @@ class OperationSchemaError(Exception):
path: Optional[str] = None
method: Optional[str] = None
full_path: Optional[str] = None
jsonschema_error: Optional[ValidationError] = None

@classmethod
def from_jsonschema_error(
Expand All @@ -174,8 +176,20 @@ def from_jsonschema_error(
message += "The provided definition doesn't match any of the expected formats or types."
else:
message += error.message
message += "\n\nEnsure that the definition complies with the OpenAPI specification"
return cls(message, path=path, method=method, full_path=full_path, jsonschema_error=error)
message += f"\n\n{SCHEMA_ERROR_SUGGESTION}"
return cls(message, path=path, method=method, full_path=full_path)

@classmethod
def from_reference_resolution_error(
cls, error: RefResolutionError, path: Optional[str], method: Optional[str], full_path: Optional[str]
) -> "OperationSchemaError":
message = "Unresolvable JSON pointer in the schema"
# Get the pointer value from "Unresolvable JSON pointer: 'components/UnknownParameter'"
pointer = str(error).split(": ", 1)[-1]
message += f"\n\nError details:\n JSON pointer: {pointer}"
message += "\n This typically means that the schema is referencing a component that doesn't exist."
message += f"\n\n{SCHEMA_ERROR_SUGGESTION}"
return cls(message, path=path, method=method, full_path=full_path)

def as_failing_test_function(self) -> Callable:
"""Create a test function that will fail.
Expand Down
4 changes: 4 additions & 0 deletions src/schemathesis/specs/openapi/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ def _raise_invalid_schema(
method: Optional[str] = None,
) -> NoReturn:
__tracebackhide__ = True
if isinstance(error, jsonschema.exceptions.RefResolutionError):
raise OperationSchemaError.from_reference_resolution_error(
error, path=path, method=method, full_path=full_path
) from None
try:
self.validate()
except jsonschema.ValidationError as exc:
Expand Down
19 changes: 19 additions & 0 deletions test/cli/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2408,3 +2408,22 @@ def test_invalid_schema_with_disabled_validation(testdir, cli, openapi_3_schema_
Ensure that the definition complies with the OpenAPI specification"""
in result.stdout
)


def test_unresolvable_reference_with_disabled_validation(testdir, cli, open_api_3_schema_with_recoverable_errors):
# When there is an error in the schema
schema_file = testdir.makefile(".json", schema=json.dumps(open_api_3_schema_with_recoverable_errors))
# And the validation is disabled (default)
result = cli.run(str(schema_file), "--dry-run")
assert result.exit_code == ExitCode.TESTS_FAILED, result.stdout
# Then we should show an error message derived from JSON Schema
assert (
"""OperationSchemaError: Unresolvable JSON pointer in the schema
Error details:
JSON pointer: 'components/UnknownMethods'
This typically means that the schema is referencing a component that doesn't exist.
Ensure that the definition complies with the OpenAPI specification"""
in result.stdout
)
8 changes: 4 additions & 4 deletions test/test_recoverable_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def schema(open_api_3_schema_with_recoverable_errors):
# Operation-level error
r".*test_\[POST /bar\] FAILED",
# The error in both failing cases
".*Unresolvable JSON pointer:.*",
".*OperationSchemaError: Unresolvable JSON pointer in the schema.*",
]


Expand Down Expand Up @@ -69,7 +69,7 @@ def test_(case):
# Operation-level error
r".*test_\[POST /bar\] SUBFAIL",
# The error in both failing cases
".*Unresolvable JSON pointer:.*",
".*Unresolvable JSON pointer in the schema.*",
]
result.stdout.re_match_lines(expected)

Expand Down Expand Up @@ -111,11 +111,11 @@ def test_in_cli(testdir, cli, open_api_3_schema_with_recoverable_errors, workers
assert lines[8].startswith("POST /bar E")
else:
assert lines[7] in ("E.", ".E")
error = "Unresolvable JSON pointer: 'components/UnknownParameter'"
error = " JSON pointer: 'components/UnknownParameter'"
assert len([line for line in lines if error in line]) == 1
assert "1 passed, 2 errored" in lines[-1]
assert "____ /foo ____" in result.stdout
assert "Unresolvable JSON pointer: 'components/UnknownMethods'" in result.stdout
assert " JSON pointer: 'components/UnknownMethods'" in result.stdout


def test_direct_access(schema):
Expand Down

0 comments on commit 00a707d

Please sign in to comment.