Skip to content

Commit

Permalink
fix: Silently failing to detect numeric status codes when the schema …
Browse files Browse the repository at this point in the history
…contains a shared `parameters` key

Ref: #1343
  • Loading branch information
Stranger6667 committed Dec 1, 2021
1 parent ed3762b commit b820385
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 19 deletions.
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Changelog
`Unreleased`_ - TBD
-------------------

**Fixed**

- Silently failing to detect numeric status codes when the schema contains a shared ``parameters`` key. `#1343`_

`3.11.2`_ - 2021-11-30
----------------------

Expand Down Expand Up @@ -2200,6 +2204,7 @@ Deprecated
.. _0.3.0: https://github.com/schemathesis/schemathesis/compare/v0.2.0...v0.3.0
.. _0.2.0: https://github.com/schemathesis/schemathesis/compare/v0.1.0...v0.2.0

.. _#1343: https://github.com/schemathesis/schemathesis/issues/1343
.. _#1340: https://github.com/schemathesis/schemathesis/issues/1340
.. _#1336: https://github.com/schemathesis/schemathesis/issues/1336
.. _#1331: https://github.com/schemathesis/schemathesis/issues/1331
Expand Down
30 changes: 17 additions & 13 deletions src/schemathesis/specs/openapi/loaders.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import io
import pathlib
from contextlib import suppress
from typing import IO, Any, Callable, Dict, List, Optional, Tuple, Union, cast
from typing import IO, Any, Callable, Dict, List, Optional, Set, Tuple, Union, cast
from urllib.parse import urljoin

import jsonschema
Expand Down Expand Up @@ -190,7 +189,9 @@ def from_dict(
dispatch("before_load_schema", HookContext(), raw_schema)

def init_openapi_2() -> SwaggerV20:
_maybe_validate_schema(raw_schema, definitions.SWAGGER_20_VALIDATOR, validate_schema)
_maybe_validate_schema(
raw_schema, definitions.SWAGGER_20_VALIDATOR, validate_schema, SwaggerV20.allowed_http_methods
)
return SwaggerV20(
raw_schema,
app=app,
Expand All @@ -207,7 +208,9 @@ def init_openapi_2() -> SwaggerV20:
)

def init_openapi_3() -> OpenApi30:
_maybe_validate_schema(raw_schema, definitions.OPENAPI_30_VALIDATOR, validate_schema)
_maybe_validate_schema(
raw_schema, definitions.OPENAPI_30_VALIDATOR, validate_schema, OpenApi30.allowed_http_methods
)
return OpenApi30(
raw_schema,
app=app,
Expand Down Expand Up @@ -255,21 +258,22 @@ def _format_status_codes(status_codes: List[Tuple[int, List[Union[str, int]]]])


def _maybe_validate_schema(
instance: Dict[str, Any], validator: jsonschema.validators.Draft4Validator, validate_schema: bool
instance: Dict[str, Any],
validator: jsonschema.validators.Draft4Validator,
validate_schema: bool,
allowed_http_methods: Set[str],
) -> None:
if validate_schema:
try:
validator.validate(instance)
except TypeError as exc:
if validation.is_pattern_error(exc):
# Ignore errors for completely invalid schemas - it will be covered by the re-raising after this block
with suppress(AttributeError):
status_codes = validation.find_numeric_http_status_codes(instance)
if status_codes:
message = _format_status_codes(status_codes)
raise SchemaLoadingError(f"{NUMERIC_STATUS_CODES_MESSAGE}\n{message}") from exc
# Some other pattern error
raise SchemaLoadingError(NON_STRING_OBJECT_KEY) from exc
status_codes = validation.find_numeric_http_status_codes(instance, allowed_http_methods)
if status_codes:
message = _format_status_codes(status_codes)
raise SchemaLoadingError(f"{NUMERIC_STATUS_CODES_MESSAGE}\n{message}") from exc
# Some other pattern error
raise SchemaLoadingError(NON_STRING_OBJECT_KEY) from exc
raise SchemaLoadingError("Invalid schema") from exc
except ValidationError as exc:
raise SchemaLoadingError("The input schema is not a valid Open API schema") from exc
Expand Down
19 changes: 13 additions & 6 deletions src/schemathesis/specs/openapi/validation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Tuple, Union
from typing import Any, Dict, List, Set, Tuple, Union


def is_pattern_error(exception: TypeError) -> bool:
Expand All @@ -7,11 +7,18 @@ def is_pattern_error(exception: TypeError) -> bool:
return str(exception) == "expected string or bytes-like object"


def find_numeric_http_status_codes(schema: Dict[str, Any]) -> List[Tuple[int, List[Union[str, int]]]]:
def find_numeric_http_status_codes(
schema: Dict[str, Any], allowed_http_methods: Set[str]
) -> List[Tuple[int, List[Union[str, int]]]]:
if not isinstance(schema, dict):
return []
found = []
for path, methods in schema.get("paths", {}).items():
for method, definition in methods.items():
for key in definition.get("responses", {}):
if isinstance(key, int):
found.append((key, [path, method]))
if isinstance(methods, dict):
for method, definition in methods.items():
if method not in allowed_http_methods or not isinstance(definition, dict):
continue
for key in definition.get("responses", {}):
if isinstance(key, int):
found.append((key, [path, method]))
return found
1 change: 1 addition & 0 deletions test/loaders/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def test_numeric_status_codes(empty_open_api_3_schema):
# When the API schema contains a numeric status code, which is not allowed by the spec
empty_open_api_3_schema["paths"] = {
"/foo": {
"parameters": [],
"get": {
"responses": {200: {"description": "OK"}},
},
Expand Down

0 comments on commit b820385

Please sign in to comment.