schemathesis 3.39.16__py3-none-any.whl → 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- schemathesis/__init__.py +41 -79
- schemathesis/auths.py +111 -122
- schemathesis/checks.py +169 -60
- schemathesis/cli/__init__.py +15 -2117
- schemathesis/cli/commands/__init__.py +85 -0
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +590 -0
- schemathesis/cli/commands/run/context.py +204 -0
- schemathesis/cli/commands/run/events.py +60 -0
- schemathesis/cli/commands/run/executor.py +157 -0
- schemathesis/cli/commands/run/filters.py +53 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +18 -0
- schemathesis/cli/commands/run/handlers/cassettes.py +474 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +55 -0
- schemathesis/cli/commands/run/handlers/output.py +1628 -0
- schemathesis/cli/commands/run/loaders.py +114 -0
- schemathesis/cli/commands/run/validation.py +246 -0
- schemathesis/cli/constants.py +5 -58
- schemathesis/cli/core.py +19 -0
- schemathesis/cli/ext/fs.py +16 -0
- schemathesis/cli/ext/groups.py +84 -0
- schemathesis/cli/{options.py → ext/options.py} +36 -34
- schemathesis/config/__init__.py +189 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +99 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +156 -0
- schemathesis/config/_generation.py +149 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +327 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +187 -0
- schemathesis/config/_projects.py +527 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +25 -0
- schemathesis/config/schema.json +885 -0
- schemathesis/core/__init__.py +67 -0
- schemathesis/core/compat.py +32 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +58 -0
- schemathesis/core/deserialization.py +65 -0
- schemathesis/core/errors.py +459 -0
- schemathesis/core/failures.py +315 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
- schemathesis/core/output/__init__.py +46 -0
- schemathesis/core/output/sanitization.py +54 -0
- schemathesis/{throttling.py → core/rate_limit.py} +16 -17
- schemathesis/core/registries.py +31 -0
- schemathesis/core/transforms.py +113 -0
- schemathesis/core/transport.py +223 -0
- schemathesis/core/validation.py +54 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +28 -0
- schemathesis/engine/context.py +118 -0
- schemathesis/engine/control.py +36 -0
- schemathesis/engine/core.py +169 -0
- schemathesis/engine/errors.py +464 -0
- schemathesis/engine/events.py +258 -0
- schemathesis/engine/phases/__init__.py +88 -0
- schemathesis/{runner → engine/phases}/probes.py +52 -68
- schemathesis/engine/phases/stateful/__init__.py +68 -0
- schemathesis/engine/phases/stateful/_executor.py +356 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +212 -0
- schemathesis/engine/phases/unit/_executor.py +416 -0
- schemathesis/engine/phases/unit/_pool.py +82 -0
- schemathesis/engine/recorder.py +247 -0
- schemathesis/errors.py +43 -0
- schemathesis/filters.py +17 -98
- schemathesis/generation/__init__.py +5 -33
- schemathesis/generation/case.py +317 -0
- schemathesis/generation/coverage.py +282 -175
- schemathesis/generation/hypothesis/__init__.py +36 -0
- schemathesis/generation/hypothesis/builder.py +800 -0
- schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +14 -0
- schemathesis/generation/hypothesis/strategies.py +16 -0
- schemathesis/generation/meta.py +115 -0
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +20 -0
- schemathesis/generation/overrides.py +116 -0
- schemathesis/generation/stateful/__init__.py +37 -0
- schemathesis/generation/stateful/state_machine.py +278 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +284 -0
- schemathesis/hooks.py +80 -101
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +455 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +72 -0
- schemathesis/openapi/loaders.py +313 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +281 -0
- schemathesis/pytest/loaders.py +36 -0
- schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +128 -108
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +537 -273
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +1 -2
- schemathesis/specs/graphql/scalars.py +42 -6
- schemathesis/specs/graphql/schemas.py +141 -137
- schemathesis/specs/graphql/validation.py +11 -17
- schemathesis/specs/openapi/__init__.py +6 -1
- schemathesis/specs/openapi/_cache.py +1 -2
- schemathesis/specs/openapi/_hypothesis.py +142 -156
- schemathesis/specs/openapi/checks.py +368 -257
- schemathesis/specs/openapi/converter.py +4 -4
- schemathesis/specs/openapi/definitions.py +1 -1
- schemathesis/specs/openapi/examples.py +23 -21
- schemathesis/specs/openapi/expressions/__init__.py +31 -19
- schemathesis/specs/openapi/expressions/extractors.py +1 -4
- schemathesis/specs/openapi/expressions/lexer.py +1 -1
- schemathesis/specs/openapi/expressions/nodes.py +36 -41
- schemathesis/specs/openapi/expressions/parser.py +1 -1
- schemathesis/specs/openapi/formats.py +35 -7
- schemathesis/specs/openapi/media_types.py +53 -12
- schemathesis/specs/openapi/negative/__init__.py +7 -4
- schemathesis/specs/openapi/negative/mutations.py +6 -5
- schemathesis/specs/openapi/parameters.py +7 -10
- schemathesis/specs/openapi/patterns.py +94 -31
- schemathesis/specs/openapi/references.py +12 -53
- schemathesis/specs/openapi/schemas.py +233 -307
- schemathesis/specs/openapi/security.py +1 -1
- schemathesis/specs/openapi/serialization.py +12 -6
- schemathesis/specs/openapi/stateful/__init__.py +268 -133
- schemathesis/specs/openapi/stateful/control.py +87 -0
- schemathesis/specs/openapi/stateful/links.py +209 -0
- schemathesis/transport/__init__.py +142 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +124 -0
- schemathesis/transport/requests.py +244 -0
- schemathesis/{_xml.py → transport/serialization.py} +69 -11
- schemathesis/transport/wsgi.py +171 -0
- schemathesis-4.0.0.dist-info/METADATA +204 -0
- schemathesis-4.0.0.dist-info/RECORD +164 -0
- {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/entry_points.txt +1 -1
- {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/licenses/LICENSE +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -19
- schemathesis/_hypothesis.py +0 -717
- schemathesis/_override.py +0 -50
- schemathesis/_patches.py +0 -21
- schemathesis/_rate_limiter.py +0 -7
- schemathesis/cli/callbacks.py +0 -466
- schemathesis/cli/cassettes.py +0 -561
- schemathesis/cli/context.py +0 -75
- schemathesis/cli/debug.py +0 -27
- schemathesis/cli/handlers.py +0 -19
- schemathesis/cli/junitxml.py +0 -124
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -920
- schemathesis/cli/output/short.py +0 -59
- schemathesis/cli/reporting.py +0 -79
- schemathesis/cli/sanitization.py +0 -26
- schemathesis/code_samples.py +0 -151
- schemathesis/constants.py +0 -54
- schemathesis/contrib/__init__.py +0 -11
- schemathesis/contrib/openapi/__init__.py +0 -11
- schemathesis/contrib/openapi/fill_missing_examples.py +0 -24
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -16
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -571
- schemathesis/experimental/__init__.py +0 -109
- schemathesis/extra/_aiohttp.py +0 -28
- schemathesis/extra/_flask.py +0 -13
- schemathesis/extra/_server.py +0 -18
- schemathesis/failures.py +0 -284
- schemathesis/fixups/__init__.py +0 -37
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -28
- schemathesis/generation/_methods.py +0 -44
- schemathesis/graphql.py +0 -3
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/checks.py +0 -86
- schemathesis/internal/copy.py +0 -32
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -37
- schemathesis/internal/diff.py +0 -15
- schemathesis/internal/extensions.py +0 -27
- schemathesis/internal/jsonschema.py +0 -36
- schemathesis/internal/output.py +0 -68
- schemathesis/internal/transformation.py +0 -26
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -474
- schemathesis/loaders.py +0 -122
- schemathesis/models.py +0 -1341
- schemathesis/parameters.py +0 -90
- schemathesis/runner/__init__.py +0 -605
- schemathesis/runner/events.py +0 -389
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/context.py +0 -88
- schemathesis/runner/impl/core.py +0 -1280
- schemathesis/runner/impl/solo.py +0 -80
- schemathesis/runner/impl/threadpool.py +0 -391
- schemathesis/runner/serialization.py +0 -544
- schemathesis/sanitization.py +0 -252
- schemathesis/serializers.py +0 -328
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -202
- schemathesis/service/client.py +0 -133
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -61
- schemathesis/service/extensions.py +0 -224
- schemathesis/service/hosts.py +0 -111
- schemathesis/service/metadata.py +0 -71
- schemathesis/service/models.py +0 -258
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -173
- schemathesis/service/usage.py +0 -66
- schemathesis/specs/graphql/loaders.py +0 -364
- schemathesis/specs/openapi/expressions/context.py +0 -16
- schemathesis/specs/openapi/links.py +0 -389
- schemathesis/specs/openapi/loaders.py +0 -707
- schemathesis/specs/openapi/stateful/statistic.py +0 -198
- schemathesis/specs/openapi/stateful/types.py +0 -14
- schemathesis/specs/openapi/validation.py +0 -26
- schemathesis/stateful/__init__.py +0 -147
- schemathesis/stateful/config.py +0 -97
- schemathesis/stateful/context.py +0 -135
- schemathesis/stateful/events.py +0 -274
- schemathesis/stateful/runner.py +0 -309
- schemathesis/stateful/sink.py +0 -68
- schemathesis/stateful/state_machine.py +0 -328
- schemathesis/stateful/statistic.py +0 -22
- schemathesis/stateful/validation.py +0 -100
- schemathesis/targets.py +0 -77
- schemathesis/transports/__init__.py +0 -369
- schemathesis/transports/asgi.py +0 -7
- schemathesis/transports/auth.py +0 -38
- schemathesis/transports/headers.py +0 -36
- schemathesis/transports/responses.py +0 -57
- schemathesis/types.py +0 -44
- schemathesis/utils.py +0 -164
- schemathesis-3.39.16.dist-info/METADATA +0 -293
- schemathesis-3.39.16.dist-info/RECORD +0 -160
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
- /schemathesis/{internal → core}/result.py +0 -0
- {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
import click
|
7
|
+
from tomli import TOMLDecodeError
|
8
|
+
|
9
|
+
from schemathesis.cli.commands.data import Data
|
10
|
+
from schemathesis.cli.commands.run import run as run_command
|
11
|
+
from schemathesis.cli.commands.run.handlers.output import display_header
|
12
|
+
from schemathesis.cli.constants import EXTENSIONS_DOCUMENTATION_URL
|
13
|
+
from schemathesis.cli.core import get_terminal_width
|
14
|
+
from schemathesis.cli.ext.groups import CommandWithGroupedOptions, GroupedOption
|
15
|
+
from schemathesis.config import ConfigError, SchemathesisConfig
|
16
|
+
from schemathesis.core.errors import HookError, format_exception
|
17
|
+
from schemathesis.core.version import SCHEMATHESIS_VERSION
|
18
|
+
|
19
|
+
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
20
|
+
|
21
|
+
|
22
|
+
@click.group(context_settings=CONTEXT_SETTINGS) # type: ignore[misc]
|
23
|
+
@click.option( # type: ignore[misc]
|
24
|
+
"--config-file",
|
25
|
+
"config_file",
|
26
|
+
help="The path to `schemathesis.toml` file to use for configuration",
|
27
|
+
metavar="PATH",
|
28
|
+
type=str,
|
29
|
+
)
|
30
|
+
@click.pass_context # type: ignore[misc]
|
31
|
+
@click.version_option() # type: ignore[misc]
|
32
|
+
def schemathesis(ctx: click.Context, config_file: str | None) -> None:
|
33
|
+
"""Property-based API testing for OpenAPI and GraphQL."""
|
34
|
+
try:
|
35
|
+
if config_file is not None:
|
36
|
+
config = SchemathesisConfig.from_path(config_file)
|
37
|
+
else:
|
38
|
+
config = SchemathesisConfig.discover()
|
39
|
+
except (TOMLDecodeError, ConfigError) as exc:
|
40
|
+
display_header(SCHEMATHESIS_VERSION)
|
41
|
+
click.secho(
|
42
|
+
f"❌ Failed to load configuration file{f' from {config_file}' if config_file else ''}",
|
43
|
+
fg="red",
|
44
|
+
bold=True,
|
45
|
+
)
|
46
|
+
if isinstance(exc, TOMLDecodeError):
|
47
|
+
detail = "The configuration file content is not valid TOML"
|
48
|
+
else:
|
49
|
+
detail = "The loaded configuration is incorrect"
|
50
|
+
click.echo(f"\n{detail}\n\n{exc}")
|
51
|
+
ctx.exit(1)
|
52
|
+
except HookError as exc:
|
53
|
+
click.secho("Unable to load Schemathesis extension hooks", fg="red", bold=True)
|
54
|
+
formatted_module_name = click.style(f"'{exc.module_path}'", bold=True)
|
55
|
+
cause = exc.__cause__
|
56
|
+
assert isinstance(cause, Exception)
|
57
|
+
if isinstance(cause, ModuleNotFoundError) and cause.name == exc.module_path:
|
58
|
+
click.echo(
|
59
|
+
f"\nAn attempt to import the module {formatted_module_name} failed because it could not be found."
|
60
|
+
)
|
61
|
+
click.echo("\nEnsure the module name is correctly spelled and reachable from the current directory.")
|
62
|
+
else:
|
63
|
+
click.echo(f"\nAn error occurred while importing the module {formatted_module_name}. Traceback:")
|
64
|
+
message = format_exception(cause, with_traceback=True, skip_frames=1)
|
65
|
+
click.secho(f"\n{message}", fg="red")
|
66
|
+
click.echo(f"\nFor more information on how to work with hooks, visit {EXTENSIONS_DOCUMENTATION_URL}")
|
67
|
+
ctx.exit(1)
|
68
|
+
ctx.obj = Data(config=config)
|
69
|
+
|
70
|
+
|
71
|
+
@dataclass
|
72
|
+
class Group:
|
73
|
+
name: str
|
74
|
+
|
75
|
+
__slots__ = ("name",)
|
76
|
+
|
77
|
+
def add_option(self, *args: Any, **kwargs: Any) -> None:
|
78
|
+
run.params.append(GroupedOption(args, group=self.name, **kwargs))
|
79
|
+
|
80
|
+
|
81
|
+
run = schemathesis.command(
|
82
|
+
short_help="Execute automated tests based on API specifications",
|
83
|
+
cls=CommandWithGroupedOptions,
|
84
|
+
context_settings={"terminal_width": get_terminal_width(), **CONTEXT_SETTINGS},
|
85
|
+
)(run_command)
|
@@ -0,0 +1,590 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, Callable
|
5
|
+
|
6
|
+
import click
|
7
|
+
from click.utils import LazyFile
|
8
|
+
|
9
|
+
from schemathesis.checks import CHECKS, load_all_checks
|
10
|
+
from schemathesis.cli.commands.run import executor, validation
|
11
|
+
from schemathesis.cli.commands.run.filters import with_filters
|
12
|
+
from schemathesis.cli.constants import DEFAULT_WORKERS, MAX_WORKERS, MIN_WORKERS
|
13
|
+
from schemathesis.cli.core import ensure_color
|
14
|
+
from schemathesis.cli.ext.groups import group, grouped_option
|
15
|
+
from schemathesis.cli.ext.options import (
|
16
|
+
CsvChoice,
|
17
|
+
CsvEnumChoice,
|
18
|
+
CustomHelpMessageChoice,
|
19
|
+
RegistryChoice,
|
20
|
+
)
|
21
|
+
from schemathesis.config import (
|
22
|
+
DEFAULT_REPORT_DIRECTORY,
|
23
|
+
HealthCheck,
|
24
|
+
ReportFormat,
|
25
|
+
SchemathesisConfig,
|
26
|
+
SchemathesisWarning,
|
27
|
+
)
|
28
|
+
from schemathesis.core import HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER
|
29
|
+
from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
|
30
|
+
from schemathesis.generation import GenerationMode
|
31
|
+
from schemathesis.generation.metrics import METRICS, MetricFunction
|
32
|
+
|
33
|
+
load_all_checks()
|
34
|
+
|
35
|
+
COLOR_OPTIONS_INVALID_USAGE_MESSAGE = "Can't use `--no-color` and `--force-color` simultaneously"
|
36
|
+
|
37
|
+
DEFAULT_PHASES = ["examples", "coverage", "fuzzing", "stateful"]
|
38
|
+
|
39
|
+
|
40
|
+
@click.argument( # type: ignore[misc]
|
41
|
+
"location",
|
42
|
+
type=str,
|
43
|
+
callback=validation.validate_schema_location,
|
44
|
+
)
|
45
|
+
@group("Options")
|
46
|
+
@grouped_option(
|
47
|
+
"--url",
|
48
|
+
"-u",
|
49
|
+
"base_url",
|
50
|
+
help="API base URL (required for file-based schemas)",
|
51
|
+
metavar="URL",
|
52
|
+
type=str,
|
53
|
+
callback=validation.validate_base_url,
|
54
|
+
envvar="SCHEMATHESIS_BASE_URL",
|
55
|
+
)
|
56
|
+
@grouped_option(
|
57
|
+
"--workers",
|
58
|
+
"-w",
|
59
|
+
"workers",
|
60
|
+
help="Number of concurrent workers for testing. Auto-adjusts if 'auto' is specified",
|
61
|
+
type=CustomHelpMessageChoice(
|
62
|
+
["auto", *list(map(str, range(MIN_WORKERS, MAX_WORKERS + 1)))],
|
63
|
+
choices_repr=f"[auto, {MIN_WORKERS}-{MAX_WORKERS}]",
|
64
|
+
),
|
65
|
+
default=str(DEFAULT_WORKERS),
|
66
|
+
show_default=True,
|
67
|
+
callback=validation.convert_workers,
|
68
|
+
metavar="",
|
69
|
+
)
|
70
|
+
@grouped_option(
|
71
|
+
"--phases",
|
72
|
+
help="A comma-separated list of test phases to run",
|
73
|
+
type=CsvChoice(DEFAULT_PHASES),
|
74
|
+
default=",".join(DEFAULT_PHASES),
|
75
|
+
metavar="",
|
76
|
+
)
|
77
|
+
@grouped_option(
|
78
|
+
"--suppress-health-check",
|
79
|
+
help="A comma-separated list of Schemathesis health checks to disable",
|
80
|
+
type=CsvEnumChoice(HealthCheck),
|
81
|
+
metavar="",
|
82
|
+
)
|
83
|
+
@grouped_option(
|
84
|
+
"--wait-for-schema",
|
85
|
+
help="Maximum duration, in seconds, to wait for the API schema to become available. Disabled by default",
|
86
|
+
type=click.FloatRange(1.0),
|
87
|
+
default=None,
|
88
|
+
envvar="SCHEMATHESIS_WAIT_FOR_SCHEMA",
|
89
|
+
)
|
90
|
+
@grouped_option(
|
91
|
+
"--warnings",
|
92
|
+
help="Control warning display: 'off' to disable all, or comma-separated list of warning types to enable",
|
93
|
+
type=str,
|
94
|
+
default=None,
|
95
|
+
callback=validation.validate_warnings,
|
96
|
+
metavar="WARNINGS",
|
97
|
+
)
|
98
|
+
@group("API validation options")
|
99
|
+
@grouped_option(
|
100
|
+
"--checks",
|
101
|
+
"-c",
|
102
|
+
"included_check_names",
|
103
|
+
multiple=True,
|
104
|
+
help="Comma-separated list of checks to run against API responses",
|
105
|
+
type=RegistryChoice(CHECKS, with_all=True),
|
106
|
+
default=None,
|
107
|
+
callback=validation.reduce_list,
|
108
|
+
show_default=True,
|
109
|
+
metavar="",
|
110
|
+
)
|
111
|
+
@grouped_option(
|
112
|
+
"--exclude-checks",
|
113
|
+
"excluded_check_names",
|
114
|
+
multiple=True,
|
115
|
+
help="Comma-separated list of checks to skip during testing",
|
116
|
+
type=RegistryChoice(CHECKS, with_all=True),
|
117
|
+
default=None,
|
118
|
+
callback=validation.reduce_list,
|
119
|
+
show_default=True,
|
120
|
+
metavar="",
|
121
|
+
)
|
122
|
+
@grouped_option(
|
123
|
+
"--max-failures",
|
124
|
+
"max_failures",
|
125
|
+
type=click.IntRange(min=1),
|
126
|
+
help="Terminate the test suite after reaching a specified number of failures or errors",
|
127
|
+
show_default=True,
|
128
|
+
)
|
129
|
+
@grouped_option(
|
130
|
+
"--continue-on-failure",
|
131
|
+
"continue_on_failure",
|
132
|
+
help="Continue executing all test cases within a scenario, even after encountering failures",
|
133
|
+
is_flag=True,
|
134
|
+
default=False,
|
135
|
+
metavar="",
|
136
|
+
)
|
137
|
+
@grouped_option(
|
138
|
+
"--max-response-time",
|
139
|
+
help="Maximum allowed API response time in seconds",
|
140
|
+
type=click.FloatRange(min=0.0, min_open=True),
|
141
|
+
metavar="SECONDS",
|
142
|
+
)
|
143
|
+
@group(
|
144
|
+
"Filtering options",
|
145
|
+
description=(
|
146
|
+
"Filter operations by path, method, name, tag, or operation-id using:\n\n"
|
147
|
+
"--include-TYPE VALUE Match operations with exact VALUE\n"
|
148
|
+
"--include-TYPE-regex PATTERN Match operations using regular expression\n"
|
149
|
+
"--exclude-TYPE VALUE Exclude operations with exact VALUE\n"
|
150
|
+
"--exclude-TYPE-regex PATTERN Exclude operations using regular expression"
|
151
|
+
),
|
152
|
+
)
|
153
|
+
@with_filters
|
154
|
+
@grouped_option(
|
155
|
+
"--include-by",
|
156
|
+
"include_by",
|
157
|
+
type=str,
|
158
|
+
metavar="EXPR",
|
159
|
+
callback=validation.validate_filter_expression,
|
160
|
+
help="Include using custom expression",
|
161
|
+
)
|
162
|
+
@grouped_option(
|
163
|
+
"--exclude-by",
|
164
|
+
"exclude_by",
|
165
|
+
type=str,
|
166
|
+
callback=validation.validate_filter_expression,
|
167
|
+
metavar="EXPR",
|
168
|
+
help="Exclude using custom expression",
|
169
|
+
)
|
170
|
+
@grouped_option(
|
171
|
+
"--exclude-deprecated",
|
172
|
+
help="Skip deprecated operations",
|
173
|
+
is_flag=True,
|
174
|
+
is_eager=True,
|
175
|
+
default=False,
|
176
|
+
show_default=True,
|
177
|
+
)
|
178
|
+
@group("Network requests options")
|
179
|
+
@grouped_option(
|
180
|
+
"--header",
|
181
|
+
"-H",
|
182
|
+
"headers",
|
183
|
+
help=r"Add a custom HTTP header to all API requests",
|
184
|
+
metavar="NAME:VALUE",
|
185
|
+
multiple=True,
|
186
|
+
type=str,
|
187
|
+
callback=validation.validate_headers,
|
188
|
+
)
|
189
|
+
@grouped_option(
|
190
|
+
"--auth",
|
191
|
+
"-a",
|
192
|
+
help="Authenticate all API requests with basic authentication",
|
193
|
+
metavar="USER:PASS",
|
194
|
+
type=str,
|
195
|
+
callback=validation.validate_auth,
|
196
|
+
)
|
197
|
+
@grouped_option(
|
198
|
+
"--proxy",
|
199
|
+
"request_proxy",
|
200
|
+
help="Set the proxy for all network requests",
|
201
|
+
metavar="URL",
|
202
|
+
type=str,
|
203
|
+
)
|
204
|
+
@grouped_option(
|
205
|
+
"--tls-verify",
|
206
|
+
"request_tls_verify",
|
207
|
+
help="Path to CA bundle for TLS verification, or 'false' to disable",
|
208
|
+
type=str,
|
209
|
+
default="true",
|
210
|
+
show_default=True,
|
211
|
+
callback=validation.convert_boolean_string,
|
212
|
+
)
|
213
|
+
@grouped_option(
|
214
|
+
"--rate-limit",
|
215
|
+
help="Specify a rate limit for test requests in '<limit>/<duration>' format. "
|
216
|
+
"Example - `100/m` for 100 requests per minute",
|
217
|
+
type=str,
|
218
|
+
callback=validation.validate_rate_limit,
|
219
|
+
)
|
220
|
+
@grouped_option(
|
221
|
+
"--request-timeout",
|
222
|
+
help="Timeout limit, in seconds, for each network request during tests",
|
223
|
+
type=click.FloatRange(min=0.0, min_open=True),
|
224
|
+
default=DEFAULT_RESPONSE_TIMEOUT,
|
225
|
+
)
|
226
|
+
@grouped_option(
|
227
|
+
"--request-cert",
|
228
|
+
help="File path of unencrypted client certificate for authentication. "
|
229
|
+
"The certificate can be bundled with a private key (e.g. PEM) or the private "
|
230
|
+
"key can be provided with the --request-cert-key argument",
|
231
|
+
type=click.Path(exists=True),
|
232
|
+
default=None,
|
233
|
+
show_default=False,
|
234
|
+
)
|
235
|
+
@grouped_option(
|
236
|
+
"--request-cert-key",
|
237
|
+
help="Specify the file path of the private key for the client certificate",
|
238
|
+
type=click.Path(exists=True),
|
239
|
+
default=None,
|
240
|
+
show_default=False,
|
241
|
+
callback=validation.validate_request_cert_key,
|
242
|
+
)
|
243
|
+
@group("Output options")
|
244
|
+
@grouped_option(
|
245
|
+
"--report",
|
246
|
+
"report_formats",
|
247
|
+
help="Generate test reports in formats specified as a comma-separated list",
|
248
|
+
type=CsvEnumChoice(ReportFormat),
|
249
|
+
is_eager=True,
|
250
|
+
metavar="FORMAT",
|
251
|
+
)
|
252
|
+
@grouped_option(
|
253
|
+
"--report-dir",
|
254
|
+
"report_directory",
|
255
|
+
help="Directory to store all report files",
|
256
|
+
type=click.Path(file_okay=False, dir_okay=True),
|
257
|
+
default=DEFAULT_REPORT_DIRECTORY,
|
258
|
+
show_default=True,
|
259
|
+
)
|
260
|
+
@grouped_option(
|
261
|
+
"--report-junit-path",
|
262
|
+
help="Custom path for JUnit XML report",
|
263
|
+
type=click.File("w", encoding="utf-8"),
|
264
|
+
is_eager=True,
|
265
|
+
)
|
266
|
+
@grouped_option(
|
267
|
+
"--report-vcr-path",
|
268
|
+
help="Custom path for VCR cassette",
|
269
|
+
type=click.File("w", encoding="utf-8"),
|
270
|
+
is_eager=True,
|
271
|
+
)
|
272
|
+
@grouped_option(
|
273
|
+
"--report-har-path",
|
274
|
+
help="Custom path for HAR file",
|
275
|
+
type=click.File("w", encoding="utf-8"),
|
276
|
+
is_eager=True,
|
277
|
+
)
|
278
|
+
@grouped_option(
|
279
|
+
"--report-preserve-bytes",
|
280
|
+
help="Retain exact byte sequence of payloads in cassettes, encoded as base64",
|
281
|
+
type=bool,
|
282
|
+
is_flag=True,
|
283
|
+
default=False,
|
284
|
+
callback=validation.validate_preserve_bytes,
|
285
|
+
)
|
286
|
+
@grouped_option(
|
287
|
+
"--output-sanitize",
|
288
|
+
type=str,
|
289
|
+
default="true",
|
290
|
+
show_default=True,
|
291
|
+
help="Enable or disable automatic output sanitization to obscure sensitive data",
|
292
|
+
metavar="BOOLEAN",
|
293
|
+
callback=validation.convert_boolean_string,
|
294
|
+
)
|
295
|
+
@grouped_option(
|
296
|
+
"--output-truncate",
|
297
|
+
help="Truncate schemas and responses in error messages",
|
298
|
+
type=str,
|
299
|
+
default="true",
|
300
|
+
show_default=True,
|
301
|
+
metavar="BOOLEAN",
|
302
|
+
callback=validation.convert_boolean_string,
|
303
|
+
)
|
304
|
+
@group("Data generation options")
|
305
|
+
@grouped_option(
|
306
|
+
"--mode",
|
307
|
+
"-m",
|
308
|
+
"generation_modes",
|
309
|
+
help="Test data generation mode",
|
310
|
+
type=click.Choice([item.value for item in GenerationMode] + ["all"]),
|
311
|
+
default="all",
|
312
|
+
callback=validation.convert_generation_mode,
|
313
|
+
show_default=True,
|
314
|
+
metavar="",
|
315
|
+
)
|
316
|
+
@grouped_option(
|
317
|
+
"--max-examples",
|
318
|
+
"-n",
|
319
|
+
"generation_max_examples",
|
320
|
+
help="Maximum number of test cases per API operation",
|
321
|
+
type=click.IntRange(1),
|
322
|
+
)
|
323
|
+
@grouped_option(
|
324
|
+
"--seed",
|
325
|
+
"generation_seed",
|
326
|
+
help="Random seed for reproducible test runs",
|
327
|
+
type=int,
|
328
|
+
)
|
329
|
+
@grouped_option(
|
330
|
+
"--no-shrink",
|
331
|
+
"generation_no_shrink",
|
332
|
+
help="Disable test case shrinking. Makes test failures harder to debug but improves performance",
|
333
|
+
is_flag=True,
|
334
|
+
)
|
335
|
+
@grouped_option(
|
336
|
+
"--generation-deterministic",
|
337
|
+
help="Enables deterministic mode, which eliminates random variation between tests",
|
338
|
+
is_flag=True,
|
339
|
+
is_eager=True,
|
340
|
+
default=False,
|
341
|
+
show_default=True,
|
342
|
+
)
|
343
|
+
@grouped_option(
|
344
|
+
"--generation-allow-x00",
|
345
|
+
help="Whether to allow the generation of 'NULL' bytes within strings",
|
346
|
+
type=str,
|
347
|
+
default="true",
|
348
|
+
show_default=True,
|
349
|
+
metavar="BOOLEAN",
|
350
|
+
callback=validation.convert_boolean_string,
|
351
|
+
)
|
352
|
+
@grouped_option(
|
353
|
+
"--generation-codec",
|
354
|
+
help="The codec used for generating strings",
|
355
|
+
type=str,
|
356
|
+
default="utf-8",
|
357
|
+
callback=validation.validate_generation_codec,
|
358
|
+
)
|
359
|
+
@grouped_option(
|
360
|
+
"--generation-maximize",
|
361
|
+
"generation_maximize",
|
362
|
+
multiple=True,
|
363
|
+
help="Guide input generation to values more likely to expose bugs via targeted property-based testing",
|
364
|
+
type=RegistryChoice(METRICS),
|
365
|
+
default=None,
|
366
|
+
callback=validation.convert_maximize,
|
367
|
+
show_default=True,
|
368
|
+
metavar="METRIC",
|
369
|
+
)
|
370
|
+
@grouped_option(
|
371
|
+
"--generation-with-security-parameters",
|
372
|
+
help="Whether to generate security parameters",
|
373
|
+
type=str,
|
374
|
+
default="true",
|
375
|
+
show_default=True,
|
376
|
+
callback=validation.convert_boolean_string,
|
377
|
+
metavar="BOOLEAN",
|
378
|
+
)
|
379
|
+
@grouped_option(
|
380
|
+
"--generation-graphql-allow-null",
|
381
|
+
help="Whether to use `null` values for optional arguments in GraphQL queries",
|
382
|
+
type=str,
|
383
|
+
default="true",
|
384
|
+
show_default=True,
|
385
|
+
callback=validation.convert_boolean_string,
|
386
|
+
metavar="BOOLEAN",
|
387
|
+
)
|
388
|
+
@grouped_option(
|
389
|
+
"--generation-database",
|
390
|
+
help="Storage for examples discovered by Schemathesis. "
|
391
|
+
f"Use 'none' to disable, '{HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER}' for temporary storage, "
|
392
|
+
f"or specify a file path for persistent storage",
|
393
|
+
type=str,
|
394
|
+
callback=validation.validate_hypothesis_database,
|
395
|
+
)
|
396
|
+
@grouped_option(
|
397
|
+
"--generation-unique-inputs",
|
398
|
+
"generation_unique_inputs",
|
399
|
+
help="Force the generation of unique test cases",
|
400
|
+
is_flag=True,
|
401
|
+
default=False,
|
402
|
+
show_default=True,
|
403
|
+
metavar="BOOLEAN",
|
404
|
+
)
|
405
|
+
@group("Global options")
|
406
|
+
@grouped_option("--no-color", help="Disable ANSI color escape codes", type=bool, is_flag=True)
|
407
|
+
@grouped_option("--force-color", help="Explicitly tells to enable ANSI color escape codes", type=bool, is_flag=True)
|
408
|
+
@click.pass_context # type: ignore[misc]
|
409
|
+
def run(
|
410
|
+
ctx: click.Context,
|
411
|
+
*,
|
412
|
+
location: str,
|
413
|
+
auth: tuple[str, str] | None,
|
414
|
+
headers: dict[str, str],
|
415
|
+
included_check_names: list[str] | None,
|
416
|
+
excluded_check_names: list[str] | None,
|
417
|
+
max_response_time: float | None = None,
|
418
|
+
phases: list[str] = DEFAULT_PHASES,
|
419
|
+
max_failures: int | None = None,
|
420
|
+
continue_on_failure: bool | None = None,
|
421
|
+
include_path: tuple[str, ...],
|
422
|
+
include_path_regex: str | None,
|
423
|
+
include_method: tuple[str, ...],
|
424
|
+
include_method_regex: str | None,
|
425
|
+
include_name: tuple[str, ...],
|
426
|
+
include_name_regex: str | None,
|
427
|
+
include_tag: tuple[str, ...],
|
428
|
+
include_tag_regex: str | None,
|
429
|
+
include_operation_id: tuple[str, ...],
|
430
|
+
include_operation_id_regex: str | None,
|
431
|
+
exclude_path: tuple[str, ...],
|
432
|
+
exclude_path_regex: str | None,
|
433
|
+
exclude_method: tuple[str, ...],
|
434
|
+
exclude_method_regex: str | None,
|
435
|
+
exclude_name: tuple[str, ...],
|
436
|
+
exclude_name_regex: str | None,
|
437
|
+
exclude_tag: tuple[str, ...],
|
438
|
+
exclude_tag_regex: str | None,
|
439
|
+
exclude_operation_id: tuple[str, ...],
|
440
|
+
exclude_operation_id_regex: str | None,
|
441
|
+
include_by: Callable | None = None,
|
442
|
+
exclude_by: Callable | None = None,
|
443
|
+
exclude_deprecated: bool = False,
|
444
|
+
workers: int = DEFAULT_WORKERS,
|
445
|
+
base_url: str | None,
|
446
|
+
wait_for_schema: float | None = None,
|
447
|
+
suppress_health_check: list[HealthCheck] | None,
|
448
|
+
warnings: bool | list[SchemathesisWarning] | None,
|
449
|
+
rate_limit: str | None = None,
|
450
|
+
request_timeout: int | None = None,
|
451
|
+
request_tls_verify: bool = True,
|
452
|
+
request_cert: str | None = None,
|
453
|
+
request_cert_key: str | None = None,
|
454
|
+
request_proxy: str | None = None,
|
455
|
+
report_formats: list[ReportFormat] | None,
|
456
|
+
report_directory: Path | str = DEFAULT_REPORT_DIRECTORY,
|
457
|
+
report_junit_path: LazyFile | None = None,
|
458
|
+
report_vcr_path: LazyFile | None = None,
|
459
|
+
report_har_path: LazyFile | None = None,
|
460
|
+
report_preserve_bytes: bool = False,
|
461
|
+
output_sanitize: bool = True,
|
462
|
+
output_truncate: bool = True,
|
463
|
+
generation_modes: list[GenerationMode],
|
464
|
+
generation_seed: int | None = None,
|
465
|
+
generation_max_examples: int | None = None,
|
466
|
+
generation_maximize: list[MetricFunction] | None,
|
467
|
+
generation_deterministic: bool = False,
|
468
|
+
generation_database: str | None = None,
|
469
|
+
generation_unique_inputs: bool = False,
|
470
|
+
generation_allow_x00: bool = True,
|
471
|
+
generation_graphql_allow_null: bool = True,
|
472
|
+
generation_with_security_parameters: bool = True,
|
473
|
+
generation_codec: str = "utf-8",
|
474
|
+
generation_no_shrink: bool = False,
|
475
|
+
force_color: bool = False,
|
476
|
+
no_color: bool = False,
|
477
|
+
**__kwargs: Any,
|
478
|
+
) -> None:
|
479
|
+
"""Generate and run property-based tests against your API.
|
480
|
+
|
481
|
+
\b
|
482
|
+
LOCATION can be:
|
483
|
+
- Local file: ./openapi.json, ./schema.yaml, ./schema.graphql
|
484
|
+
- OpenAPI URL: https://api.example.com/openapi.json
|
485
|
+
- GraphQL URL: https://api.example.com/graphql/
|
486
|
+
""" # noqa: D301
|
487
|
+
if no_color and force_color:
|
488
|
+
raise click.UsageError(COLOR_OPTIONS_INVALID_USAGE_MESSAGE)
|
489
|
+
|
490
|
+
config: SchemathesisConfig = ctx.obj.config
|
491
|
+
|
492
|
+
# First, set the right color
|
493
|
+
color: bool | None
|
494
|
+
if force_color:
|
495
|
+
color = True
|
496
|
+
elif no_color:
|
497
|
+
color = False
|
498
|
+
else:
|
499
|
+
color = config.color
|
500
|
+
ensure_color(ctx, color)
|
501
|
+
|
502
|
+
validation.validate_auth_overlap(auth, headers)
|
503
|
+
|
504
|
+
# Then override the global config from CLI options
|
505
|
+
config.update(
|
506
|
+
color=color,
|
507
|
+
suppress_health_check=suppress_health_check,
|
508
|
+
seed=generation_seed,
|
509
|
+
wait_for_schema=wait_for_schema,
|
510
|
+
max_failures=max_failures,
|
511
|
+
)
|
512
|
+
config.output.sanitization.update(enabled=output_sanitize)
|
513
|
+
config.output.truncation.update(enabled=output_truncate)
|
514
|
+
config.reports.update(
|
515
|
+
formats=report_formats,
|
516
|
+
junit_path=report_junit_path.name if report_junit_path else None,
|
517
|
+
vcr_path=report_vcr_path.name if report_vcr_path else None,
|
518
|
+
har_path=report_har_path.name if report_har_path else None,
|
519
|
+
directory=Path(report_directory),
|
520
|
+
preserve_bytes=report_preserve_bytes,
|
521
|
+
)
|
522
|
+
# Other CLI options work as an override for all defined projects
|
523
|
+
config.projects.override.update(
|
524
|
+
base_url=base_url,
|
525
|
+
headers=headers if headers else None,
|
526
|
+
basic_auth=auth,
|
527
|
+
workers=workers,
|
528
|
+
continue_on_failure=continue_on_failure,
|
529
|
+
rate_limit=rate_limit,
|
530
|
+
request_timeout=request_timeout,
|
531
|
+
tls_verify=request_tls_verify,
|
532
|
+
request_cert=request_cert,
|
533
|
+
request_cert_key=request_cert_key,
|
534
|
+
proxy=request_proxy,
|
535
|
+
warnings=warnings,
|
536
|
+
)
|
537
|
+
# These are filters for what API operations should be tested
|
538
|
+
filter_set = {
|
539
|
+
"include_path": include_path,
|
540
|
+
"include_method": include_method,
|
541
|
+
"include_name": include_name,
|
542
|
+
"include_tag": include_tag,
|
543
|
+
"include_operation_id": include_operation_id,
|
544
|
+
"include_path_regex": include_path_regex,
|
545
|
+
"include_method_regex": include_method_regex,
|
546
|
+
"include_name_regex": include_name_regex,
|
547
|
+
"include_tag_regex": include_tag_regex,
|
548
|
+
"include_operation_id_regex": include_operation_id_regex,
|
549
|
+
"exclude_path": exclude_path,
|
550
|
+
"exclude_method": exclude_method,
|
551
|
+
"exclude_name": exclude_name,
|
552
|
+
"exclude_tag": exclude_tag,
|
553
|
+
"exclude_operation_id": exclude_operation_id,
|
554
|
+
"exclude_path_regex": exclude_path_regex,
|
555
|
+
"exclude_method_regex": exclude_method_regex,
|
556
|
+
"exclude_name_regex": exclude_name_regex,
|
557
|
+
"exclude_tag_regex": exclude_tag_regex,
|
558
|
+
"exclude_operation_id_regex": exclude_operation_id_regex,
|
559
|
+
"include_by": include_by,
|
560
|
+
"exclude_by": exclude_by,
|
561
|
+
"exclude_deprecated": exclude_deprecated,
|
562
|
+
}
|
563
|
+
config.projects.override.phases.update(phases=phases)
|
564
|
+
config.projects.override.checks.update(
|
565
|
+
included_check_names=included_check_names,
|
566
|
+
excluded_check_names=excluded_check_names,
|
567
|
+
max_response_time=max_response_time,
|
568
|
+
)
|
569
|
+
config.projects.override.generation.update(
|
570
|
+
modes=generation_modes,
|
571
|
+
max_examples=generation_max_examples,
|
572
|
+
no_shrink=generation_no_shrink,
|
573
|
+
maximize=generation_maximize,
|
574
|
+
deterministic=generation_deterministic,
|
575
|
+
database=generation_database,
|
576
|
+
unique_inputs=generation_unique_inputs,
|
577
|
+
allow_x00=generation_allow_x00,
|
578
|
+
graphql_allow_null=generation_graphql_allow_null,
|
579
|
+
with_security_parameters=generation_with_security_parameters,
|
580
|
+
codec=generation_codec,
|
581
|
+
)
|
582
|
+
|
583
|
+
executor.execute(
|
584
|
+
location=location,
|
585
|
+
filter_set=filter_set,
|
586
|
+
# We don't the project yet, so pass the default config
|
587
|
+
config=config.projects.get_default(),
|
588
|
+
args=ctx.args,
|
589
|
+
params=ctx.params,
|
590
|
+
)
|