schemathesis 3.25.5__py3-none-any.whl → 4.0.0a1__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 +27 -65
- schemathesis/auths.py +102 -82
- schemathesis/checks.py +126 -46
- schemathesis/cli/__init__.py +11 -1766
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/commands/__init__.py +37 -0
- schemathesis/cli/commands/run/__init__.py +662 -0
- schemathesis/cli/commands/run/checks.py +80 -0
- schemathesis/cli/commands/run/context.py +117 -0
- schemathesis/cli/commands/run/events.py +35 -0
- schemathesis/cli/commands/run/executor.py +138 -0
- schemathesis/cli/commands/run/filters.py +194 -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 +494 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
- schemathesis/cli/commands/run/handlers/output.py +746 -0
- schemathesis/cli/commands/run/hypothesis.py +105 -0
- schemathesis/cli/commands/run/loaders.py +129 -0
- schemathesis/cli/{callbacks.py → commands/run/validation.py} +103 -174
- schemathesis/cli/constants.py +5 -52
- schemathesis/cli/core.py +17 -0
- schemathesis/cli/ext/fs.py +14 -0
- schemathesis/cli/ext/groups.py +55 -0
- schemathesis/cli/{options.py → ext/options.py} +39 -10
- schemathesis/cli/hooks.py +36 -0
- schemathesis/contrib/__init__.py +1 -3
- schemathesis/contrib/openapi/__init__.py +1 -3
- schemathesis/contrib/openapi/fill_missing_examples.py +3 -5
- schemathesis/core/__init__.py +58 -0
- schemathesis/core/compat.py +25 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +58 -0
- schemathesis/core/deserialization.py +65 -0
- schemathesis/core/errors.py +370 -0
- schemathesis/core/failures.py +285 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/{_lazy_import.py → core/lazy_import.py} +1 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +17 -13
- schemathesis/core/output/__init__.py +69 -0
- schemathesis/core/output/sanitization.py +197 -0
- schemathesis/core/rate_limit.py +60 -0
- schemathesis/core/registries.py +31 -0
- schemathesis/{internal → core}/result.py +1 -1
- schemathesis/core/transforms.py +113 -0
- schemathesis/core/transport.py +108 -0
- schemathesis/core/validation.py +38 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +30 -0
- schemathesis/engine/config.py +59 -0
- schemathesis/engine/context.py +119 -0
- schemathesis/engine/control.py +36 -0
- schemathesis/engine/core.py +157 -0
- schemathesis/engine/errors.py +394 -0
- schemathesis/engine/events.py +337 -0
- schemathesis/engine/phases/__init__.py +66 -0
- schemathesis/{cli → engine/phases}/probes.py +63 -70
- schemathesis/engine/phases/stateful/__init__.py +65 -0
- schemathesis/engine/phases/stateful/_executor.py +326 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +174 -0
- schemathesis/engine/phases/unit/_executor.py +321 -0
- schemathesis/engine/phases/unit/_pool.py +74 -0
- schemathesis/engine/recorder.py +241 -0
- schemathesis/errors.py +31 -0
- schemathesis/experimental/__init__.py +18 -14
- schemathesis/filters.py +103 -14
- schemathesis/generation/__init__.py +21 -37
- schemathesis/generation/case.py +190 -0
- schemathesis/generation/coverage.py +931 -0
- schemathesis/generation/hypothesis/__init__.py +30 -0
- schemathesis/generation/hypothesis/builder.py +585 -0
- schemathesis/generation/hypothesis/examples.py +50 -0
- 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/modes.py +28 -0
- schemathesis/generation/overrides.py +96 -0
- schemathesis/generation/stateful/__init__.py +20 -0
- schemathesis/{stateful → generation/stateful}/state_machine.py +68 -81
- schemathesis/generation/targets.py +69 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +115 -0
- schemathesis/graphql/loaders.py +131 -0
- schemathesis/hooks.py +99 -67
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +412 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +63 -0
- schemathesis/openapi/loaders.py +178 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +273 -0
- schemathesis/pytest/loaders.py +12 -0
- schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +106 -127
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +537 -261
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +25 -0
- schemathesis/specs/graphql/nodes.py +1 -0
- schemathesis/specs/graphql/scalars.py +7 -5
- schemathesis/specs/graphql/schemas.py +215 -187
- schemathesis/specs/graphql/validation.py +11 -18
- schemathesis/specs/openapi/__init__.py +7 -1
- schemathesis/specs/openapi/_cache.py +122 -0
- schemathesis/specs/openapi/_hypothesis.py +146 -165
- schemathesis/specs/openapi/checks.py +565 -67
- schemathesis/specs/openapi/converter.py +33 -6
- schemathesis/specs/openapi/definitions.py +11 -18
- schemathesis/specs/openapi/examples.py +153 -39
- schemathesis/specs/openapi/expressions/__init__.py +37 -2
- schemathesis/specs/openapi/expressions/context.py +4 -6
- schemathesis/specs/openapi/expressions/extractors.py +23 -0
- schemathesis/specs/openapi/expressions/lexer.py +20 -18
- schemathesis/specs/openapi/expressions/nodes.py +38 -14
- schemathesis/specs/openapi/expressions/parser.py +26 -5
- schemathesis/specs/openapi/formats.py +45 -0
- schemathesis/specs/openapi/links.py +65 -165
- schemathesis/specs/openapi/media_types.py +32 -0
- schemathesis/specs/openapi/negative/__init__.py +7 -3
- schemathesis/specs/openapi/negative/mutations.py +24 -8
- schemathesis/specs/openapi/parameters.py +46 -30
- schemathesis/specs/openapi/patterns.py +137 -0
- schemathesis/specs/openapi/references.py +47 -57
- schemathesis/specs/openapi/schemas.py +483 -367
- schemathesis/specs/openapi/security.py +25 -7
- schemathesis/specs/openapi/serialization.py +11 -6
- schemathesis/specs/openapi/stateful/__init__.py +185 -73
- schemathesis/specs/openapi/utils.py +6 -1
- schemathesis/transport/__init__.py +104 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +99 -0
- schemathesis/transport/requests.py +221 -0
- schemathesis/{_xml.py → transport/serialization.py} +143 -28
- schemathesis/transport/wsgi.py +165 -0
- schemathesis-4.0.0a1.dist-info/METADATA +297 -0
- schemathesis-4.0.0a1.dist-info/RECORD +152 -0
- {schemathesis-3.25.5.dist-info → schemathesis-4.0.0a1.dist-info}/WHEEL +1 -1
- {schemathesis-3.25.5.dist-info → schemathesis-4.0.0a1.dist-info}/entry_points.txt +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -17
- schemathesis/_hypothesis.py +0 -246
- schemathesis/_override.py +0 -49
- schemathesis/cli/cassettes.py +0 -375
- schemathesis/cli/context.py +0 -55
- schemathesis/cli/debug.py +0 -26
- schemathesis/cli/handlers.py +0 -16
- schemathesis/cli/junitxml.py +0 -43
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -765
- schemathesis/cli/output/short.py +0 -40
- schemathesis/cli/sanitization.py +0 -20
- schemathesis/code_samples.py +0 -149
- schemathesis/constants.py +0 -55
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -15
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -560
- schemathesis/extra/_aiohttp.py +0 -27
- schemathesis/extra/_flask.py +0 -10
- schemathesis/extra/_server.py +0 -17
- schemathesis/failures.py +0 -209
- schemathesis/fixups/__init__.py +0 -36
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -29
- schemathesis/graphql.py +0 -4
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/copy.py +0 -13
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -34
- schemathesis/internal/jsonschema.py +0 -35
- schemathesis/internal/transformation.py +0 -15
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -361
- schemathesis/loaders.py +0 -120
- schemathesis/models.py +0 -1231
- schemathesis/parameters.py +0 -86
- schemathesis/runner/__init__.py +0 -555
- schemathesis/runner/events.py +0 -309
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/core.py +0 -986
- schemathesis/runner/impl/solo.py +0 -90
- schemathesis/runner/impl/threadpool.py +0 -400
- schemathesis/runner/serialization.py +0 -411
- schemathesis/sanitization.py +0 -248
- schemathesis/serializers.py +0 -315
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -201
- schemathesis/service/client.py +0 -100
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -57
- schemathesis/service/hosts.py +0 -107
- schemathesis/service/metadata.py +0 -46
- schemathesis/service/models.py +0 -49
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -184
- schemathesis/service/usage.py +0 -65
- schemathesis/specs/graphql/loaders.py +0 -344
- schemathesis/specs/openapi/filters.py +0 -49
- schemathesis/specs/openapi/loaders.py +0 -667
- schemathesis/specs/openapi/stateful/links.py +0 -92
- schemathesis/specs/openapi/validation.py +0 -25
- schemathesis/stateful/__init__.py +0 -133
- schemathesis/targets.py +0 -45
- schemathesis/throttling.py +0 -41
- schemathesis/transports/__init__.py +0 -5
- schemathesis/transports/auth.py +0 -15
- schemathesis/transports/headers.py +0 -35
- schemathesis/transports/responses.py +0 -52
- schemathesis/types.py +0 -35
- schemathesis/utils.py +0 -169
- schemathesis-3.25.5.dist-info/METADATA +0 -356
- schemathesis-3.25.5.dist-info/RECORD +0 -134
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- {schemathesis-3.25.5.dist-info → schemathesis-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
import click
|
7
|
+
|
8
|
+
from schemathesis.cli import hooks
|
9
|
+
from schemathesis.cli.commands.run import run as run_command
|
10
|
+
from schemathesis.cli.core import get_terminal_width
|
11
|
+
from schemathesis.cli.ext.groups import CommandWithGroupedOptions, GroupedOption
|
12
|
+
|
13
|
+
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
14
|
+
|
15
|
+
|
16
|
+
@click.group(context_settings=CONTEXT_SETTINGS) # type: ignore[misc]
|
17
|
+
@click.version_option() # type: ignore[misc]
|
18
|
+
def schemathesis() -> None:
|
19
|
+
"""Property-based API testing for OpenAPI and GraphQL."""
|
20
|
+
hooks.load()
|
21
|
+
|
22
|
+
|
23
|
+
@dataclass
|
24
|
+
class Group:
|
25
|
+
name: str
|
26
|
+
|
27
|
+
__slots__ = ("name",)
|
28
|
+
|
29
|
+
def add_option(self, *args: Any, **kwargs: Any) -> None:
|
30
|
+
run.params.append(GroupedOption(args, group=self.name, **kwargs))
|
31
|
+
|
32
|
+
|
33
|
+
run = schemathesis.command(
|
34
|
+
short_help="Execute automated tests based on API specifications",
|
35
|
+
cls=CommandWithGroupedOptions,
|
36
|
+
context_settings={"terminal_width": get_terminal_width(), **CONTEXT_SETTINGS},
|
37
|
+
)(run_command)
|
@@ -0,0 +1,662 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from random import Random
|
4
|
+
from typing import Any, Sequence
|
5
|
+
|
6
|
+
import click
|
7
|
+
|
8
|
+
from schemathesis import contrib, experimental
|
9
|
+
from schemathesis.checks import CHECKS
|
10
|
+
from schemathesis.cli.commands.run import executor, validation
|
11
|
+
from schemathesis.cli.commands.run.checks import CheckArguments
|
12
|
+
from schemathesis.cli.commands.run.filters import FilterArguments, with_filters
|
13
|
+
from schemathesis.cli.commands.run.handlers.cassettes import CassetteConfig, CassetteFormat
|
14
|
+
from schemathesis.cli.commands.run.hypothesis import (
|
15
|
+
HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER,
|
16
|
+
HealthCheck,
|
17
|
+
Phase,
|
18
|
+
prepare_health_checks,
|
19
|
+
prepare_phases,
|
20
|
+
prepare_settings,
|
21
|
+
)
|
22
|
+
from schemathesis.cli.constants import DEFAULT_WORKERS, MAX_WORKERS, MIN_WORKERS
|
23
|
+
from schemathesis.cli.core import ensure_color
|
24
|
+
from schemathesis.cli.ext.groups import group, grouped_option
|
25
|
+
from schemathesis.cli.ext.options import (
|
26
|
+
CsvChoice,
|
27
|
+
CsvEnumChoice,
|
28
|
+
CsvListChoice,
|
29
|
+
CustomHelpMessageChoice,
|
30
|
+
RegistryChoice,
|
31
|
+
)
|
32
|
+
from schemathesis.core.output import OutputConfig
|
33
|
+
from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
|
34
|
+
from schemathesis.engine.config import EngineConfig, ExecutionConfig, NetworkConfig
|
35
|
+
from schemathesis.engine.phases import PhaseName
|
36
|
+
from schemathesis.generation import DEFAULT_GENERATOR_MODES, GenerationConfig, GenerationMode
|
37
|
+
from schemathesis.generation.overrides import Override
|
38
|
+
from schemathesis.generation.targets import TARGETS
|
39
|
+
|
40
|
+
# NOTE: Need to explicitly import all registered checks
|
41
|
+
from schemathesis.specs.openapi.checks import * # noqa: F401, F403
|
42
|
+
|
43
|
+
COLOR_OPTIONS_INVALID_USAGE_MESSAGE = "Can't use `--no-color` and `--force-color` simultaneously"
|
44
|
+
|
45
|
+
DEFAULT_PHASES = ("unit", "stateful")
|
46
|
+
|
47
|
+
|
48
|
+
@click.argument("schema", type=str) # type: ignore[misc]
|
49
|
+
@group("Options")
|
50
|
+
@grouped_option(
|
51
|
+
"--phases",
|
52
|
+
help="A comma-separated list of test phases to run",
|
53
|
+
type=CsvChoice(["unit", "stateful"]),
|
54
|
+
default=",".join(DEFAULT_PHASES),
|
55
|
+
metavar="",
|
56
|
+
)
|
57
|
+
@grouped_option(
|
58
|
+
"--base-url",
|
59
|
+
"-b",
|
60
|
+
help="Base URL of the API, required when schema is provided as a file",
|
61
|
+
type=str,
|
62
|
+
callback=validation.validate_base_url,
|
63
|
+
envvar="SCHEMATHESIS_BASE_URL",
|
64
|
+
)
|
65
|
+
@grouped_option(
|
66
|
+
"--suppress-health-check",
|
67
|
+
help="A comma-separated list of Schemathesis health checks to disable",
|
68
|
+
type=CsvEnumChoice(HealthCheck),
|
69
|
+
metavar="",
|
70
|
+
)
|
71
|
+
@grouped_option(
|
72
|
+
"--workers",
|
73
|
+
"-w",
|
74
|
+
"workers_num",
|
75
|
+
help="Number of concurrent workers for testing. Auto-adjusts if 'auto' is specified",
|
76
|
+
type=CustomHelpMessageChoice(
|
77
|
+
["auto", *list(map(str, range(MIN_WORKERS, MAX_WORKERS + 1)))],
|
78
|
+
choices_repr=f"[auto, {MIN_WORKERS}-{MAX_WORKERS}]",
|
79
|
+
),
|
80
|
+
default=str(DEFAULT_WORKERS),
|
81
|
+
show_default=True,
|
82
|
+
callback=validation.convert_workers,
|
83
|
+
metavar="",
|
84
|
+
)
|
85
|
+
@group("API validation options")
|
86
|
+
@grouped_option(
|
87
|
+
"--checks",
|
88
|
+
"-c",
|
89
|
+
"included_check_names",
|
90
|
+
multiple=True,
|
91
|
+
help="Comma-separated list of checks to run against API responses",
|
92
|
+
type=RegistryChoice(CHECKS, with_all=True),
|
93
|
+
default=("not_a_server_error",),
|
94
|
+
callback=validation.convert_checks,
|
95
|
+
show_default=True,
|
96
|
+
metavar="",
|
97
|
+
)
|
98
|
+
@grouped_option(
|
99
|
+
"--exclude-checks",
|
100
|
+
"excluded_check_names",
|
101
|
+
multiple=True,
|
102
|
+
help="Comma-separated list of checks to skip during testing",
|
103
|
+
type=RegistryChoice(CHECKS, with_all=True),
|
104
|
+
default=(),
|
105
|
+
callback=validation.convert_checks,
|
106
|
+
show_default=True,
|
107
|
+
metavar="",
|
108
|
+
)
|
109
|
+
@grouped_option(
|
110
|
+
"--max-response-time",
|
111
|
+
help="Time limit in seconds for API response times. The test will fail if a response time exceeds this limit",
|
112
|
+
type=click.FloatRange(min=0.0, min_open=True),
|
113
|
+
)
|
114
|
+
@grouped_option(
|
115
|
+
"-x",
|
116
|
+
"--exitfirst",
|
117
|
+
"exit_first",
|
118
|
+
is_flag=True,
|
119
|
+
default=False,
|
120
|
+
help="Terminate the test suite immediately upon the first failure or error encountered",
|
121
|
+
show_default=True,
|
122
|
+
)
|
123
|
+
@grouped_option(
|
124
|
+
"--max-failures",
|
125
|
+
"max_failures",
|
126
|
+
type=click.IntRange(min=1),
|
127
|
+
help="Terminate the test suite after reaching a specified number of failures or errors",
|
128
|
+
show_default=True,
|
129
|
+
)
|
130
|
+
@group("Filtering options")
|
131
|
+
@with_filters
|
132
|
+
@grouped_option(
|
133
|
+
"--include-by",
|
134
|
+
"include_by",
|
135
|
+
type=str,
|
136
|
+
help="Include API operations by expression",
|
137
|
+
)
|
138
|
+
@grouped_option(
|
139
|
+
"--exclude-by",
|
140
|
+
"exclude_by",
|
141
|
+
type=str,
|
142
|
+
help="Exclude API operations by expression",
|
143
|
+
)
|
144
|
+
@grouped_option(
|
145
|
+
"--exclude-deprecated",
|
146
|
+
help="Exclude deprecated API operations from testing",
|
147
|
+
is_flag=True,
|
148
|
+
is_eager=True,
|
149
|
+
default=False,
|
150
|
+
show_default=True,
|
151
|
+
)
|
152
|
+
@group("Loader options")
|
153
|
+
@grouped_option(
|
154
|
+
"--wait-for-schema",
|
155
|
+
help="Maximum duration, in seconds, to wait for the API schema to become available. Disabled by default",
|
156
|
+
type=click.FloatRange(1.0),
|
157
|
+
default=None,
|
158
|
+
envvar="SCHEMATHESIS_WAIT_FOR_SCHEMA",
|
159
|
+
)
|
160
|
+
@group("Network requests options")
|
161
|
+
@grouped_option(
|
162
|
+
"--header",
|
163
|
+
"-H",
|
164
|
+
"headers",
|
165
|
+
help=r"Add a custom HTTP header to all API requests. Format: 'Header-Name: Value'",
|
166
|
+
multiple=True,
|
167
|
+
type=str,
|
168
|
+
callback=validation.validate_headers,
|
169
|
+
)
|
170
|
+
@grouped_option(
|
171
|
+
"--auth",
|
172
|
+
"-a",
|
173
|
+
help="Provide the server authentication details in the 'USER:PASSWORD' format",
|
174
|
+
type=str,
|
175
|
+
callback=validation.validate_auth,
|
176
|
+
)
|
177
|
+
@grouped_option(
|
178
|
+
"--request-timeout",
|
179
|
+
help="Timeout limit, in seconds, for each network request during tests",
|
180
|
+
type=click.FloatRange(min=0.0, min_open=True),
|
181
|
+
default=DEFAULT_RESPONSE_TIMEOUT,
|
182
|
+
)
|
183
|
+
@grouped_option(
|
184
|
+
"--request-proxy",
|
185
|
+
help="Set the proxy for all network requests",
|
186
|
+
type=str,
|
187
|
+
)
|
188
|
+
@grouped_option(
|
189
|
+
"--request-tls-verify",
|
190
|
+
help="Configures TLS certificate verification for server requests. Can specify path to CA_BUNDLE for custom certs",
|
191
|
+
type=str,
|
192
|
+
default="true",
|
193
|
+
show_default=True,
|
194
|
+
callback=validation.convert_boolean_string,
|
195
|
+
)
|
196
|
+
@grouped_option(
|
197
|
+
"--request-cert",
|
198
|
+
help="File path of unencrypted client certificate for authentication. "
|
199
|
+
"The certificate can be bundled with a private key (e.g. PEM) or the private "
|
200
|
+
"key can be provided with the --request-cert-key argument",
|
201
|
+
type=click.Path(exists=True),
|
202
|
+
default=None,
|
203
|
+
show_default=False,
|
204
|
+
)
|
205
|
+
@grouped_option(
|
206
|
+
"--request-cert-key",
|
207
|
+
help="Specify the file path of the private key for the client certificate",
|
208
|
+
type=click.Path(exists=True),
|
209
|
+
default=None,
|
210
|
+
show_default=False,
|
211
|
+
callback=validation.validate_request_cert_key,
|
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
|
+
@group("Output options")
|
221
|
+
@grouped_option(
|
222
|
+
"--junit-xml",
|
223
|
+
help="Output a JUnit-XML style report at the specified file path",
|
224
|
+
type=click.File("w", encoding="utf-8"),
|
225
|
+
)
|
226
|
+
@grouped_option(
|
227
|
+
"--cassette-path",
|
228
|
+
help="Save the test outcomes in a VCR-compatible format",
|
229
|
+
type=click.File("w", encoding="utf-8"),
|
230
|
+
is_eager=True,
|
231
|
+
)
|
232
|
+
@grouped_option(
|
233
|
+
"--cassette-format",
|
234
|
+
help="Format of the saved cassettes",
|
235
|
+
type=click.Choice([item.name.lower() for item in CassetteFormat]),
|
236
|
+
default=CassetteFormat.VCR.name.lower(),
|
237
|
+
callback=validation.convert_cassette_format,
|
238
|
+
metavar="",
|
239
|
+
)
|
240
|
+
@grouped_option(
|
241
|
+
"--cassette-preserve-exact-body-bytes",
|
242
|
+
help="Retain exact byte sequence of payloads in cassettes, encoded as base64",
|
243
|
+
is_flag=True,
|
244
|
+
callback=validation.validate_preserve_exact_body_bytes,
|
245
|
+
)
|
246
|
+
@grouped_option(
|
247
|
+
"--output-sanitize",
|
248
|
+
type=bool,
|
249
|
+
default=True,
|
250
|
+
show_default=True,
|
251
|
+
help="Enable or disable automatic output sanitization to obscure sensitive data",
|
252
|
+
)
|
253
|
+
@grouped_option(
|
254
|
+
"--output-truncate",
|
255
|
+
help="Truncate schemas and responses in error messages",
|
256
|
+
type=str,
|
257
|
+
default="true",
|
258
|
+
show_default=True,
|
259
|
+
callback=validation.convert_boolean_string,
|
260
|
+
)
|
261
|
+
@group("Experimental options")
|
262
|
+
@grouped_option(
|
263
|
+
"--experimental",
|
264
|
+
"experiments",
|
265
|
+
help="Enable experimental features",
|
266
|
+
type=click.Choice(sorted([experiment.label for experiment in experimental.GLOBAL_EXPERIMENTS.available])),
|
267
|
+
callback=validation.convert_experimental,
|
268
|
+
multiple=True,
|
269
|
+
metavar="FEATURES",
|
270
|
+
)
|
271
|
+
@grouped_option(
|
272
|
+
"--experimental-no-failfast",
|
273
|
+
"no_failfast",
|
274
|
+
help="Continue testing an API operation after a failure is found",
|
275
|
+
is_flag=True,
|
276
|
+
default=False,
|
277
|
+
metavar="",
|
278
|
+
envvar="SCHEMATHESIS_EXPERIMENTAL_NO_FAILFAST",
|
279
|
+
)
|
280
|
+
@grouped_option(
|
281
|
+
"--experimental-missing-required-header-allowed-statuses",
|
282
|
+
"missing_required_header_allowed_statuses",
|
283
|
+
help="Comma-separated list of status codes expected for test cases with a missing required header",
|
284
|
+
type=CsvListChoice(),
|
285
|
+
callback=validation.convert_status_codes,
|
286
|
+
metavar="",
|
287
|
+
envvar="SCHEMATHESIS_EXPERIMENTAL_MISSING_REQUIRED_HEADER_ALLOWED_STATUSES",
|
288
|
+
)
|
289
|
+
@grouped_option(
|
290
|
+
"--experimental-positive-data-acceptance-allowed-statuses",
|
291
|
+
"positive_data_acceptance_allowed_statuses",
|
292
|
+
help="Comma-separated list of status codes considered as successful responses",
|
293
|
+
type=CsvListChoice(),
|
294
|
+
callback=validation.convert_status_codes,
|
295
|
+
metavar="",
|
296
|
+
envvar="SCHEMATHESIS_EXPERIMENTAL_POSITIVE_DATA_ACCEPTANCE_ALLOWED_STATUSES",
|
297
|
+
)
|
298
|
+
@grouped_option(
|
299
|
+
"--experimental-negative-data-rejection-allowed-statuses",
|
300
|
+
"negative_data_rejection_allowed_statuses",
|
301
|
+
help="Comma-separated list of status codes expected for rejected negative data",
|
302
|
+
type=CsvListChoice(),
|
303
|
+
callback=validation.convert_status_codes,
|
304
|
+
metavar="",
|
305
|
+
envvar="SCHEMATHESIS_EXPERIMENTAL_NEGATIVE_DATA_REJECTION_ALLOWED_STATUSES",
|
306
|
+
)
|
307
|
+
@group("Data generation options")
|
308
|
+
@grouped_option(
|
309
|
+
"--generation-mode",
|
310
|
+
"generation_modes",
|
311
|
+
help="Specify the approach Schemathesis uses to generate test data. "
|
312
|
+
"Use 'positive' for valid data, 'negative' for invalid data, or 'all' for both",
|
313
|
+
type=click.Choice([item.value for item in GenerationMode] + ["all"]),
|
314
|
+
default=GenerationMode.default().value,
|
315
|
+
callback=validation.convert_generation_mode,
|
316
|
+
show_default=True,
|
317
|
+
metavar="",
|
318
|
+
)
|
319
|
+
@grouped_option(
|
320
|
+
"--generation-seed",
|
321
|
+
help="Seed value for Schemathesis, ensuring reproducibility across test runs",
|
322
|
+
type=int,
|
323
|
+
)
|
324
|
+
@grouped_option(
|
325
|
+
"--generation-max-examples",
|
326
|
+
help="The cap on the number of examples generated by Schemathesis for each API operation",
|
327
|
+
type=click.IntRange(1),
|
328
|
+
)
|
329
|
+
@grouped_option(
|
330
|
+
"--generation-deterministic",
|
331
|
+
help="Enables deterministic mode, which eliminates random variation between tests",
|
332
|
+
is_flag=True,
|
333
|
+
is_eager=True,
|
334
|
+
default=None,
|
335
|
+
show_default=True,
|
336
|
+
)
|
337
|
+
@grouped_option(
|
338
|
+
"--generation-allow-x00",
|
339
|
+
help="Whether to allow the generation of `\x00` bytes within strings",
|
340
|
+
type=str,
|
341
|
+
default="true",
|
342
|
+
show_default=True,
|
343
|
+
callback=validation.convert_boolean_string,
|
344
|
+
)
|
345
|
+
@grouped_option(
|
346
|
+
"--generation-codec",
|
347
|
+
help="The codec used for generating strings",
|
348
|
+
type=str,
|
349
|
+
default="utf-8",
|
350
|
+
callback=validation.validate_generation_codec,
|
351
|
+
)
|
352
|
+
@grouped_option(
|
353
|
+
"--generation-optimize",
|
354
|
+
"generation_optimize",
|
355
|
+
multiple=True,
|
356
|
+
help="Guide input generation to values more likely to expose bugs via targeted property-based testing",
|
357
|
+
type=RegistryChoice(TARGETS),
|
358
|
+
default=None,
|
359
|
+
callback=validation.convert_checks,
|
360
|
+
show_default=True,
|
361
|
+
metavar="TARGET",
|
362
|
+
)
|
363
|
+
@grouped_option(
|
364
|
+
"--generation-with-security-parameters",
|
365
|
+
help="Whether to generate security parameters",
|
366
|
+
type=str,
|
367
|
+
default="true",
|
368
|
+
show_default=True,
|
369
|
+
callback=validation.convert_boolean_string,
|
370
|
+
metavar="BOOLEAN",
|
371
|
+
)
|
372
|
+
@grouped_option(
|
373
|
+
"--generation-graphql-allow-null",
|
374
|
+
help="Whether to use `null` values for optional arguments in GraphQL queries",
|
375
|
+
type=str,
|
376
|
+
default="true",
|
377
|
+
show_default=True,
|
378
|
+
callback=validation.convert_boolean_string,
|
379
|
+
metavar="BOOLEAN",
|
380
|
+
)
|
381
|
+
@grouped_option(
|
382
|
+
"--generation-database",
|
383
|
+
help="Storage for examples discovered by Schemathesis. "
|
384
|
+
f"Use 'none' to disable, '{HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER}' for temporary storage, "
|
385
|
+
f"or specify a file path for persistent storage",
|
386
|
+
type=str,
|
387
|
+
callback=validation.validate_hypothesis_database,
|
388
|
+
)
|
389
|
+
@grouped_option(
|
390
|
+
"--generation-unique-inputs",
|
391
|
+
"generation_unique_inputs",
|
392
|
+
help="Force the generation of unique test cases",
|
393
|
+
is_flag=True,
|
394
|
+
default=False,
|
395
|
+
show_default=True,
|
396
|
+
metavar="BOOLEAN",
|
397
|
+
)
|
398
|
+
@grouped_option(
|
399
|
+
"--contrib-openapi-fill-missing-examples",
|
400
|
+
"contrib_openapi_fill_missing_examples",
|
401
|
+
help="Enable generation of random examples for API operations that do not have explicit examples",
|
402
|
+
is_flag=True,
|
403
|
+
default=False,
|
404
|
+
show_default=True,
|
405
|
+
metavar="BOOLEAN",
|
406
|
+
)
|
407
|
+
@group("Open API options")
|
408
|
+
@grouped_option(
|
409
|
+
"--set-query",
|
410
|
+
"set_query",
|
411
|
+
help=r"OpenAPI: Override a specific query parameter by specifying 'parameter=value'",
|
412
|
+
multiple=True,
|
413
|
+
type=str,
|
414
|
+
callback=validation.validate_set_query,
|
415
|
+
)
|
416
|
+
@grouped_option(
|
417
|
+
"--set-header",
|
418
|
+
"set_header",
|
419
|
+
help=r"OpenAPI: Override a specific header parameter by specifying 'parameter=value'",
|
420
|
+
multiple=True,
|
421
|
+
type=str,
|
422
|
+
callback=validation.validate_set_header,
|
423
|
+
)
|
424
|
+
@grouped_option(
|
425
|
+
"--set-cookie",
|
426
|
+
"set_cookie",
|
427
|
+
help=r"OpenAPI: Override a specific cookie parameter by specifying 'parameter=value'",
|
428
|
+
multiple=True,
|
429
|
+
type=str,
|
430
|
+
callback=validation.validate_set_cookie,
|
431
|
+
)
|
432
|
+
@grouped_option(
|
433
|
+
"--set-path",
|
434
|
+
"set_path",
|
435
|
+
help=r"OpenAPI: Override a specific path parameter by specifying 'parameter=value'",
|
436
|
+
multiple=True,
|
437
|
+
type=str,
|
438
|
+
callback=validation.validate_set_path,
|
439
|
+
)
|
440
|
+
@group("Hypothesis engine options")
|
441
|
+
@grouped_option(
|
442
|
+
"--hypothesis-phases",
|
443
|
+
help="Testing phases to execute",
|
444
|
+
type=CsvEnumChoice(Phase),
|
445
|
+
metavar="",
|
446
|
+
)
|
447
|
+
@grouped_option(
|
448
|
+
"--hypothesis-no-phases",
|
449
|
+
help="Testing phases to exclude from execution",
|
450
|
+
type=CsvEnumChoice(Phase),
|
451
|
+
metavar="",
|
452
|
+
)
|
453
|
+
@group("Global options")
|
454
|
+
@grouped_option("--no-color", help="Disable ANSI color escape codes", type=bool, is_flag=True)
|
455
|
+
@grouped_option("--force-color", help="Explicitly tells to enable ANSI color escape codes", type=bool, is_flag=True)
|
456
|
+
@click.pass_context # type: ignore[misc]
|
457
|
+
def run(
|
458
|
+
ctx: click.Context,
|
459
|
+
schema: str,
|
460
|
+
auth: tuple[str, str] | None,
|
461
|
+
headers: dict[str, str],
|
462
|
+
set_query: dict[str, str],
|
463
|
+
set_header: dict[str, str],
|
464
|
+
set_cookie: dict[str, str],
|
465
|
+
set_path: dict[str, str],
|
466
|
+
experiments: list,
|
467
|
+
no_failfast: bool,
|
468
|
+
missing_required_header_allowed_statuses: list[str],
|
469
|
+
positive_data_acceptance_allowed_statuses: list[str],
|
470
|
+
negative_data_rejection_allowed_statuses: list[str],
|
471
|
+
included_check_names: Sequence[str],
|
472
|
+
excluded_check_names: Sequence[str],
|
473
|
+
phases: Sequence[str] = DEFAULT_PHASES,
|
474
|
+
max_response_time: float | None = None,
|
475
|
+
exit_first: bool = False,
|
476
|
+
max_failures: int | None = None,
|
477
|
+
include_path: Sequence[str] = (),
|
478
|
+
include_path_regex: str | None = None,
|
479
|
+
include_method: Sequence[str] = (),
|
480
|
+
include_method_regex: str | None = None,
|
481
|
+
include_name: Sequence[str] = (),
|
482
|
+
include_name_regex: str | None = None,
|
483
|
+
include_tag: Sequence[str] = (),
|
484
|
+
include_tag_regex: str | None = None,
|
485
|
+
include_operation_id: Sequence[str] = (),
|
486
|
+
include_operation_id_regex: str | None = None,
|
487
|
+
exclude_path: Sequence[str] = (),
|
488
|
+
exclude_path_regex: str | None = None,
|
489
|
+
exclude_method: Sequence[str] = (),
|
490
|
+
exclude_method_regex: str | None = None,
|
491
|
+
exclude_name: Sequence[str] = (),
|
492
|
+
exclude_name_regex: str | None = None,
|
493
|
+
exclude_tag: Sequence[str] = (),
|
494
|
+
exclude_tag_regex: str | None = None,
|
495
|
+
exclude_operation_id: Sequence[str] = (),
|
496
|
+
exclude_operation_id_regex: str | None = None,
|
497
|
+
include_by: str | None = None,
|
498
|
+
exclude_by: str | None = None,
|
499
|
+
exclude_deprecated: bool = False,
|
500
|
+
workers_num: int = DEFAULT_WORKERS,
|
501
|
+
base_url: str | None = None,
|
502
|
+
suppress_health_check: list[HealthCheck] | None = None,
|
503
|
+
request_timeout: int | None = None,
|
504
|
+
request_tls_verify: bool = True,
|
505
|
+
request_cert: str | None = None,
|
506
|
+
request_cert_key: str | None = None,
|
507
|
+
request_proxy: str | None = None,
|
508
|
+
junit_xml: click.utils.LazyFile | None = None,
|
509
|
+
cassette_path: click.utils.LazyFile | None = None,
|
510
|
+
cassette_format: CassetteFormat = CassetteFormat.VCR,
|
511
|
+
cassette_preserve_exact_body_bytes: bool = False,
|
512
|
+
wait_for_schema: float | None = None,
|
513
|
+
rate_limit: str | None = None,
|
514
|
+
output_sanitize: bool = True,
|
515
|
+
output_truncate: bool = True,
|
516
|
+
contrib_openapi_fill_missing_examples: bool = False,
|
517
|
+
hypothesis_phases: list[Phase] | None = None,
|
518
|
+
hypothesis_no_phases: list[Phase] | None = None,
|
519
|
+
generation_modes: tuple[GenerationMode, ...] = DEFAULT_GENERATOR_MODES,
|
520
|
+
generation_seed: int | None = None,
|
521
|
+
generation_max_examples: int | None = None,
|
522
|
+
generation_optimize: Sequence[str] | None = None,
|
523
|
+
generation_deterministic: bool | None = None,
|
524
|
+
generation_database: str | None = None,
|
525
|
+
generation_unique_inputs: bool = False,
|
526
|
+
generation_allow_x00: bool = True,
|
527
|
+
generation_graphql_allow_null: bool = True,
|
528
|
+
generation_with_security_parameters: bool = True,
|
529
|
+
generation_codec: str = "utf-8",
|
530
|
+
force_color: bool = False,
|
531
|
+
no_color: bool = False,
|
532
|
+
**__kwargs: Any,
|
533
|
+
) -> None:
|
534
|
+
"""Run tests against an API using a specified SCHEMA.
|
535
|
+
|
536
|
+
[Required] SCHEMA: Path to an OpenAPI (`.json`, `.yml`) or GraphQL SDL file, or a URL pointing to such specifications
|
537
|
+
"""
|
538
|
+
if no_color and force_color:
|
539
|
+
raise click.UsageError(COLOR_OPTIONS_INVALID_USAGE_MESSAGE)
|
540
|
+
ensure_color(ctx, no_color, force_color)
|
541
|
+
|
542
|
+
validation.validate_schema(schema, base_url)
|
543
|
+
|
544
|
+
_hypothesis_phases = prepare_phases(hypothesis_phases, hypothesis_no_phases)
|
545
|
+
_hypothesis_suppress_health_check = prepare_health_checks(suppress_health_check)
|
546
|
+
|
547
|
+
for experiment in experiments:
|
548
|
+
experiment.enable()
|
549
|
+
if contrib_openapi_fill_missing_examples:
|
550
|
+
contrib.openapi.fill_missing_examples.install()
|
551
|
+
|
552
|
+
override = Override(query=set_query, headers=set_header, cookies=set_cookie, path_parameters=set_path)
|
553
|
+
|
554
|
+
validation.validate_auth_overlap(auth, headers, override)
|
555
|
+
|
556
|
+
filter_set = FilterArguments(
|
557
|
+
include_path=include_path,
|
558
|
+
include_method=include_method,
|
559
|
+
include_name=include_name,
|
560
|
+
include_tag=include_tag,
|
561
|
+
include_operation_id=include_operation_id,
|
562
|
+
include_path_regex=include_path_regex,
|
563
|
+
include_method_regex=include_method_regex,
|
564
|
+
include_name_regex=include_name_regex,
|
565
|
+
include_tag_regex=include_tag_regex,
|
566
|
+
include_operation_id_regex=include_operation_id_regex,
|
567
|
+
exclude_path=exclude_path,
|
568
|
+
exclude_method=exclude_method,
|
569
|
+
exclude_name=exclude_name,
|
570
|
+
exclude_tag=exclude_tag,
|
571
|
+
exclude_operation_id=exclude_operation_id,
|
572
|
+
exclude_path_regex=exclude_path_regex,
|
573
|
+
exclude_method_regex=exclude_method_regex,
|
574
|
+
exclude_name_regex=exclude_name_regex,
|
575
|
+
exclude_tag_regex=exclude_tag_regex,
|
576
|
+
exclude_operation_id_regex=exclude_operation_id_regex,
|
577
|
+
include_by=include_by,
|
578
|
+
exclude_by=exclude_by,
|
579
|
+
exclude_deprecated=exclude_deprecated,
|
580
|
+
).into()
|
581
|
+
|
582
|
+
selected_checks, checks_config = CheckArguments(
|
583
|
+
included_check_names=included_check_names,
|
584
|
+
excluded_check_names=excluded_check_names,
|
585
|
+
positive_data_acceptance_allowed_statuses=positive_data_acceptance_allowed_statuses,
|
586
|
+
missing_required_header_allowed_statuses=missing_required_header_allowed_statuses,
|
587
|
+
negative_data_rejection_allowed_statuses=negative_data_rejection_allowed_statuses,
|
588
|
+
max_response_time=max_response_time,
|
589
|
+
).into()
|
590
|
+
|
591
|
+
if exit_first:
|
592
|
+
max_failures = 1
|
593
|
+
|
594
|
+
cassette_config = None
|
595
|
+
if cassette_path is not None:
|
596
|
+
cassette_config = CassetteConfig(
|
597
|
+
path=cassette_path,
|
598
|
+
format=cassette_format,
|
599
|
+
sanitize_output=output_sanitize,
|
600
|
+
preserve_exact_body_bytes=cassette_preserve_exact_body_bytes,
|
601
|
+
)
|
602
|
+
|
603
|
+
# Use the same seed for all tests unless `derandomize=True` is used
|
604
|
+
seed: int | None
|
605
|
+
if generation_seed is None and not generation_deterministic:
|
606
|
+
seed = Random().getrandbits(128)
|
607
|
+
else:
|
608
|
+
seed = generation_seed
|
609
|
+
|
610
|
+
phases_ = [PhaseName.PROBING] + [PhaseName.from_str(phase) for phase in phases]
|
611
|
+
|
612
|
+
config = executor.RunConfig(
|
613
|
+
location=schema,
|
614
|
+
base_url=base_url,
|
615
|
+
engine=EngineConfig(
|
616
|
+
execution=ExecutionConfig(
|
617
|
+
phases=phases_,
|
618
|
+
checks=selected_checks,
|
619
|
+
targets=TARGETS.get_by_names(generation_optimize or []),
|
620
|
+
hypothesis_settings=prepare_settings(
|
621
|
+
database=generation_database,
|
622
|
+
derandomize=generation_deterministic,
|
623
|
+
max_examples=generation_max_examples,
|
624
|
+
phases=_hypothesis_phases,
|
625
|
+
suppress_health_check=_hypothesis_suppress_health_check,
|
626
|
+
),
|
627
|
+
generation=GenerationConfig(
|
628
|
+
modes=list(generation_modes),
|
629
|
+
allow_x00=generation_allow_x00,
|
630
|
+
graphql_allow_null=generation_graphql_allow_null,
|
631
|
+
codec=generation_codec,
|
632
|
+
with_security_parameters=generation_with_security_parameters,
|
633
|
+
),
|
634
|
+
max_failures=max_failures,
|
635
|
+
no_failfast=no_failfast,
|
636
|
+
unique_inputs=generation_unique_inputs,
|
637
|
+
seed=seed,
|
638
|
+
workers_num=workers_num,
|
639
|
+
),
|
640
|
+
network=NetworkConfig(
|
641
|
+
auth=auth,
|
642
|
+
headers=headers,
|
643
|
+
timeout=request_timeout,
|
644
|
+
tls_verify=request_tls_verify,
|
645
|
+
proxy=request_proxy,
|
646
|
+
cert=(request_cert, request_cert_key)
|
647
|
+
if request_cert is not None and request_cert_key is not None
|
648
|
+
else request_cert,
|
649
|
+
),
|
650
|
+
override=override,
|
651
|
+
checks_config=checks_config,
|
652
|
+
),
|
653
|
+
filter_set=filter_set,
|
654
|
+
wait_for_schema=wait_for_schema,
|
655
|
+
rate_limit=rate_limit,
|
656
|
+
output=OutputConfig(sanitize=output_sanitize, truncate=output_truncate),
|
657
|
+
cassette=cassette_config,
|
658
|
+
junit_xml=junit_xml,
|
659
|
+
args=ctx.args,
|
660
|
+
params=ctx.params,
|
661
|
+
)
|
662
|
+
executor.execute(config)
|