Skip to content

Commit

Permalink
feat: Ability to skip deprecated endpoints with `--skip-deprecated-en…
Browse files Browse the repository at this point in the history
…dpoints` CLI option and `skip_deprecated_endpoints=True` argument to schema loaders

Ref: #715
  • Loading branch information
Stranger6667 committed Oct 6, 2020
1 parent 2442206 commit 20277ee
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 2 deletions.
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Changelog
- New ``port`` parameter added to ``from_uri()`` method. `#706`_
- A code snippet to reproduce a failed check when running Python tests. `#793`_
- Python 3.9 support. `#731`_
- Ability to skip deprecated endpoints with ``--skip-deprecated-endpoints`` CLI option and ``skip_deprecated_endpoints=True`` argument to schema loaders. `#715`_

**Fixed**

Expand Down Expand Up @@ -1404,6 +1405,7 @@ Deprecated
.. _#717: https://github.com/schemathesis/schemathesis/issues/717
.. _#718: https://github.com/schemathesis/schemathesis/issues/718
.. _#716: https://github.com/schemathesis/schemathesis/issues/716
.. _#715: https://github.com/schemathesis/schemathesis/issues/715
.. _#708: https://github.com/schemathesis/schemathesis/issues/708
.. _#706: https://github.com/schemathesis/schemathesis/issues/706
.. _#705: https://github.com/schemathesis/schemathesis/issues/705
Expand Down
7 changes: 7 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ For example, the following command will select all endpoints which paths start w
$ schemathesis run -E ^/api/users http://api.com/swagger.json
If your API contains deprecated endpoints (that have ``deprecated: true`` in their definition),
then you can skip them by passing ``--skip-deprecated-endpoints``:

.. code:: bash
$ schemathesis run --skip-deprecated-endpoints ...
Tests configuration
-------------------

Expand Down
10 changes: 10 additions & 0 deletions docs/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ For example, the following test selects all endpoints which paths start with ``/
def test_api(case):
case.call_and_validate()
If your API contains deprecated endpoints (that have ``deprecated: true`` in their definition),
then you can skip them by passing ``skip_deprecated_endpoints=True`` to loaders or to the `schema.parametrize` call:

.. code:: python
schema = schemathesis.from_uri(
"http://example.com/swagger.json",
skip_deprecated_endpoints=True
)
Tests configuration
-------------------

Expand Down
9 changes: 9 additions & 0 deletions src/schemathesis/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ def schemathesis(pre_run: Optional[str] = None) -> None:
type=click.IntRange(1),
)
@click.option("--validate-schema", help="Enable or disable validation of input schema.", type=bool, default=True)
@click.option(
"--skip-deprecated-endpoints",
help="Skip testing of deprecated endpoints.",
is_flag=True,
is_eager=True,
default=False,
)
@click.option(
"--junit-xml", help="Create junit-xml style report file at given path.", type=click.File("w", encoding="utf-8")
)
Expand Down Expand Up @@ -264,6 +271,7 @@ def run( # pylint: disable=too-many-arguments
app: Optional[str] = None,
request_timeout: Optional[int] = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
junit_xml: Optional[click.utils.LazyFile] = None,
show_errors_tracebacks: bool = False,
store_network_log: Optional[click.utils.LazyFile] = None,
Expand Down Expand Up @@ -312,6 +320,7 @@ def run( # pylint: disable=too-many-arguments
targets=selected_targets,
workers_num=workers_num,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
fixups=fixups,
stateful=stateful,
stateful_recursion_limit=stateful_recursion_limit,
Expand Down
6 changes: 6 additions & 0 deletions src/schemathesis/lazy.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pylint: disable=too-many-instance-attributes
from inspect import signature
from typing import Any, Callable, Dict, Optional, Union

Expand All @@ -23,6 +24,7 @@ class LazySchema:
operation_id: Optional[Filter] = attr.ib(default=NOT_SET) # pragma: no mutate
hooks: HookDispatcher = attr.ib(factory=lambda: HookDispatcher(scope=HookScope.SCHEMA)) # pragma: no mutate
validate_schema: bool = attr.ib(default=True) # pragma: no mutate
skip_deprecated_endpoints: bool = attr.ib(default=False) # pragma: no mutate

def parametrize( # pylint: disable=too-many-arguments
self,
Expand All @@ -31,6 +33,7 @@ def parametrize( # pylint: disable=too-many-arguments
tag: Optional[Filter] = NOT_SET,
operation_id: Optional[Filter] = NOT_SET,
validate_schema: Union[bool, NotSet] = NOT_SET,
skip_deprecated_endpoints: Union[bool, NotSet] = NOT_SET,
) -> Callable:
if method is NOT_SET:
method = self.method
Expand All @@ -56,6 +59,7 @@ def test(request: FixtureRequest, subtests: SubTests) -> None:
hooks=self.hooks,
test_function=func,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)
fixtures = get_fixtures(func, request)
# Changing the node id is required for better reporting - the method and endpoint will appear there
Expand Down Expand Up @@ -111,6 +115,7 @@ def get_schema(
test_function: GenericTest,
hooks: HookDispatcher,
validate_schema: Union[bool, NotSet] = NOT_SET,
skip_deprecated_endpoints: Union[bool, NotSet] = NOT_SET,
) -> BaseSchema:
"""Loads a schema from the fixture."""
# pylint: disable=too-many-arguments
Expand All @@ -125,6 +130,7 @@ def get_schema(
test_function=test_function,
hooks=hooks,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)


Expand Down
17 changes: 17 additions & 0 deletions src/schemathesis/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def from_path(
*,
app: Any = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
) -> BaseOpenAPISchema:
"""Load a file from OS path and parse to schema instance."""
with open(path) as fd:
Expand All @@ -45,6 +46,7 @@ def from_path(
operation_id=operation_id,
app=app,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)


Expand All @@ -59,6 +61,7 @@ def from_uri(
*,
app: Any = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
**kwargs: Any,
) -> BaseOpenAPISchema:
"""Load a remote resource and parse to schema instance."""
Expand All @@ -82,6 +85,7 @@ def from_uri(
operation_id=operation_id,
app=app,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)


Expand All @@ -96,6 +100,7 @@ def from_file(
*,
app: Any = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
**kwargs: Any, # needed in the runner to have compatible API across all loaders
) -> BaseOpenAPISchema:
"""Load a file content and parse to schema instance.
Expand All @@ -113,6 +118,7 @@ def from_file(
operation_id=operation_id,
app=app,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)


Expand All @@ -127,6 +133,7 @@ def from_dict(
*,
app: Any = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
) -> BaseOpenAPISchema:
"""Get a proper abstraction for the given raw schema."""
dispatch("before_load_schema", HookContext(), raw_schema)
Expand All @@ -142,6 +149,7 @@ def from_dict(
operation_id=operation_id,
app=app,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)

if "openapi" in raw_schema:
Expand All @@ -156,6 +164,7 @@ def from_dict(
operation_id=operation_id,
app=app,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)
raise ValueError("Unsupported schema type")

Expand All @@ -175,6 +184,7 @@ def from_pytest_fixture(
tag: Optional[Filter] = NOT_SET,
operation_id: Optional[Filter] = NOT_SET,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
) -> LazySchema:
"""Needed for a consistent library API."""
return LazySchema(
Expand All @@ -184,6 +194,7 @@ def from_pytest_fixture(
tag=tag,
operation_id=operation_id,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)


Expand All @@ -196,6 +207,7 @@ def from_wsgi(
tag: Optional[Filter] = None,
operation_id: Optional[Filter] = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
**kwargs: Any,
) -> BaseOpenAPISchema:
headers = kwargs.setdefault("headers", {})
Expand All @@ -217,6 +229,7 @@ def from_wsgi(
operation_id=operation_id,
app=app,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)


Expand All @@ -238,6 +251,7 @@ def from_aiohttp(
operation_id: Optional[Filter] = None,
*,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
**kwargs: Any,
) -> BaseOpenAPISchema:
from .extra._aiohttp import run_server # pylint: disable=import-outside-toplevel
Expand All @@ -253,6 +267,7 @@ def from_aiohttp(
tag=tag,
operation_id=operation_id,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
**kwargs,
)

Expand All @@ -265,6 +280,7 @@ def from_asgi(
endpoint: Optional[Filter] = None,
tag: Optional[Filter] = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
**kwargs: Any,
) -> BaseOpenAPISchema:
headers = kwargs.setdefault("headers", {})
Expand All @@ -285,4 +301,5 @@ def from_asgi(
tag=tag,
app=app,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
)
12 changes: 11 additions & 1 deletion src/schemathesis/runner/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def prepare( # pylint: disable=too-many-arguments
operation_id: Optional[Filter] = None,
app: Optional[str] = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
# Hypothesis-specific configuration
hypothesis_deadline: Optional[Union[int, NotSet]] = None,
hypothesis_derandomize: Optional[bool] = None,
Expand Down Expand Up @@ -89,6 +90,7 @@ def prepare( # pylint: disable=too-many-arguments
operation_id=operation_id,
app=app,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
checks=checks,
max_response_time=max_response_time,
targets=targets,
Expand Down Expand Up @@ -137,6 +139,7 @@ def execute_from_schema(
operation_id: Optional[Filter] = None,
app: Optional[str] = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
checks: Iterable[CheckFunction],
max_response_time: Optional[int] = None,
targets: Iterable[Target],
Expand Down Expand Up @@ -173,6 +176,7 @@ def execute_from_schema(
loader=loader,
app=app,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
auth=auth,
auth_type=auth_type,
headers=headers,
Expand Down Expand Up @@ -300,6 +304,7 @@ def load_schema(
loader: Callable = loaders.from_uri,
app: Any = None,
validate_schema: bool = True,
skip_deprecated_endpoints: bool = False,
# Network request parameters
auth: Optional[Tuple[str, str]] = None,
auth_type: Optional[str] = None,
Expand Down Expand Up @@ -329,7 +334,12 @@ def load_schema(
if loader is loaders.from_uri and loader_options.get("auth"):
loader_options["auth"] = get_requests_auth(loader_options["auth"], loader_options.pop("auth_type", None))

return loader(schema_uri, validate_schema=validate_schema, **loader_options)
return loader(
schema_uri,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
**loader_options,
)


def prepare_hypothesis_options( # pylint: disable=too-many-arguments
Expand Down
7 changes: 7 additions & 0 deletions src/schemathesis/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class BaseSchema(Mapping):
hooks: HookDispatcher = attr.ib(factory=lambda: HookDispatcher(scope=HookScope.SCHEMA)) # pragma: no mutate
test_function: Optional[GenericTest] = attr.ib(default=None) # pragma: no mutate
validate_schema: bool = attr.ib(default=True) # pragma: no mutate
skip_deprecated_endpoints: bool = attr.ib(default=False) # pragma: no mutate
stateful: Optional[Stateful] = attr.ib(default=None) # pragma: no mutate
stateful_recursion_limit: int = attr.ib(default=DEFAULT_STATEFUL_RECURSION_LIMIT) # pragma: no mutate

Expand Down Expand Up @@ -131,6 +132,7 @@ def parametrize( # pylint: disable=too-many-arguments
tag: Optional[Filter] = NOT_SET,
operation_id: Optional[Filter] = NOT_SET,
validate_schema: Union[bool, NotSet] = NOT_SET,
skip_deprecated_endpoints: Union[bool, NotSet] = NOT_SET,
stateful: Optional[Union[Stateful, NotSet]] = NOT_SET,
stateful_recursion_limit: Union[int, NotSet] = NOT_SET,
) -> Callable:
Expand All @@ -145,6 +147,7 @@ def wrapper(func: GenericTest) -> GenericTest:
tag=tag,
operation_id=operation_id,
validate_schema=validate_schema,
skip_deprecated_endpoints=skip_deprecated_endpoints,
stateful=stateful,
stateful_recursion_limit=stateful_recursion_limit,
)
Expand Down Expand Up @@ -172,6 +175,7 @@ def clone( # pylint: disable=too-many-arguments
operation_id: Optional[Filter] = NOT_SET,
hooks: Union[HookDispatcher, NotSet] = NOT_SET,
validate_schema: Union[bool, NotSet] = NOT_SET,
skip_deprecated_endpoints: Union[bool, NotSet] = NOT_SET,
stateful: Optional[Union[Stateful, NotSet]] = NOT_SET,
stateful_recursion_limit: Union[int, NotSet] = NOT_SET,
) -> "BaseSchema":
Expand All @@ -185,6 +189,8 @@ def clone( # pylint: disable=too-many-arguments
operation_id = self.operation_id
if validate_schema is NOT_SET:
validate_schema = self.validate_schema
if skip_deprecated_endpoints is NOT_SET:
skip_deprecated_endpoints = self.skip_deprecated_endpoints
if hooks is NOT_SET:
hooks = self.hooks
if stateful is NOT_SET:
Expand All @@ -204,6 +210,7 @@ def clone( # pylint: disable=too-many-arguments
hooks=hooks, # type: ignore
test_function=test_function,
validate_schema=validate_schema, # type: ignore
skip_deprecated_endpoints=skip_deprecated_endpoints, # type: ignore
stateful=stateful, # type: ignore
stateful_recursion_limit=stateful_recursion_limit, # type: ignore
)
Expand Down
4 changes: 4 additions & 0 deletions src/schemathesis/specs/openapi/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ def should_skip_by_operation_id(operation_id: Optional[str], pattern: Optional[F
return not _match_any_pattern(operation_id, pattern)


def should_skip_deprecated(is_deprecated: bool, skip_deprecated_endpoints: bool) -> bool:
return skip_deprecated_endpoints and is_deprecated


def _match_any_pattern(target: str, pattern: Filter) -> bool:
patterns = force_tuple(pattern)
return any(re.search(item, target) for item in patterns)
11 changes: 10 additions & 1 deletion src/schemathesis/specs/openapi/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
from ._hypothesis import get_case_strategy
from .converter import to_json_schema_recursive
from .examples import get_strategies_from_examples
from .filters import should_skip_by_operation_id, should_skip_by_tag, should_skip_endpoint, should_skip_method
from .filters import (
should_skip_by_operation_id,
should_skip_by_tag,
should_skip_deprecated,
should_skip_endpoint,
should_skip_method,
)
from .references import ConvertingResolver
from .security import BaseSecurityProcessor, OpenAPISecurityProcessor, SwaggerSecurityProcessor

Expand Down Expand Up @@ -62,6 +68,9 @@ def get_all_endpoints(self) -> Generator[Endpoint, None, None]:
if (
method not in self.operations
or should_skip_method(method, self.method)
or should_skip_deprecated(
resolved_definition.get("deprecated", False), self.skip_deprecated_endpoints
)
or should_skip_by_tag(resolved_definition.get("tags"), self.tag)
or should_skip_by_operation_id(resolved_definition.get("operationId"), self.operation_id)
):
Expand Down

0 comments on commit 20277ee

Please sign in to comment.