schemathesis 3.39.7__py3-none-any.whl → 4.0.0a2__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 +26 -68
- schemathesis/checks.py +130 -60
- schemathesis/cli/__init__.py +5 -2105
- 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 +30 -0
- schemathesis/cli/commands/run/executor.py +141 -0
- schemathesis/cli/commands/run/filters.py +202 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +18 -0
- schemathesis/cli/{cassettes.py → commands/run/handlers/cassettes.py} +178 -247
- schemathesis/cli/commands/run/handlers/junitxml.py +54 -0
- schemathesis/cli/commands/run/handlers/output.py +1368 -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} +59 -175
- schemathesis/cli/constants.py +5 -58
- 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} +37 -16
- 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 -7
- 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 +315 -0
- schemathesis/core/fs.py +19 -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/{internal/output.py → core/output/__init__.py} +1 -0
- schemathesis/core/output/sanitization.py +197 -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 +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 +243 -0
- schemathesis/engine/phases/__init__.py +66 -0
- schemathesis/{runner → engine/phases}/probes.py +49 -68
- schemathesis/engine/phases/stateful/__init__.py +66 -0
- schemathesis/engine/phases/stateful/_executor.py +301 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +175 -0
- schemathesis/engine/phases/unit/_executor.py +322 -0
- schemathesis/engine/phases/unit/_pool.py +74 -0
- schemathesis/engine/recorder.py +246 -0
- schemathesis/errors.py +31 -0
- schemathesis/experimental/__init__.py +9 -40
- schemathesis/filters.py +7 -95
- schemathesis/generation/__init__.py +3 -3
- schemathesis/generation/case.py +190 -0
- schemathesis/generation/coverage.py +22 -22
- schemathesis/{_patches.py → generation/hypothesis/__init__.py} +15 -6
- schemathesis/generation/hypothesis/builder.py +585 -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/modes.py +28 -0
- schemathesis/generation/overrides.py +96 -0
- schemathesis/generation/stateful/__init__.py +20 -0
- schemathesis/{stateful → generation/stateful}/state_machine.py +84 -109
- schemathesis/generation/targets.py +69 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +131 -0
- schemathesis/hooks.py +17 -62
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +387 -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} +94 -107
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +456 -228
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +1 -2
- schemathesis/specs/graphql/scalars.py +5 -3
- schemathesis/specs/graphql/schemas.py +122 -123
- 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 +97 -134
- schemathesis/specs/openapi/checks.py +238 -219
- schemathesis/specs/openapi/converter.py +4 -4
- schemathesis/specs/openapi/definitions.py +1 -1
- schemathesis/specs/openapi/examples.py +22 -20
- schemathesis/specs/openapi/expressions/__init__.py +11 -15
- schemathesis/specs/openapi/expressions/extractors.py +1 -4
- schemathesis/specs/openapi/expressions/nodes.py +33 -32
- schemathesis/specs/openapi/formats.py +3 -2
- schemathesis/specs/openapi/links.py +123 -299
- schemathesis/specs/openapi/media_types.py +10 -12
- schemathesis/specs/openapi/negative/__init__.py +2 -1
- schemathesis/specs/openapi/negative/mutations.py +3 -2
- schemathesis/specs/openapi/parameters.py +8 -6
- schemathesis/specs/openapi/patterns.py +1 -1
- schemathesis/specs/openapi/references.py +11 -51
- schemathesis/specs/openapi/schemas.py +177 -191
- schemathesis/specs/openapi/security.py +1 -1
- schemathesis/specs/openapi/serialization.py +10 -6
- schemathesis/specs/openapi/stateful/__init__.py +97 -91
- 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} +69 -7
- schemathesis/transport/wsgi.py +165 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/METADATA +18 -14
- schemathesis-4.0.0a2.dist-info/RECORD +151 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/entry_points.txt +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -19
- schemathesis/_hypothesis.py +0 -559
- schemathesis/_override.py +0 -50
- schemathesis/_rate_limiter.py +0 -7
- 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 -936
- 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 -56
- 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/extra/_aiohttp.py +0 -28
- schemathesis/extra/_flask.py +0 -13
- schemathesis/extra/_server.py +0 -18
- schemathesis/failures.py +0 -277
- 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 -84
- schemathesis/internal/copy.py +0 -32
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -38
- schemathesis/internal/diff.py +0 -15
- schemathesis/internal/extensions.py +0 -27
- schemathesis/internal/jsonschema.py +0 -36
- 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 -104
- schemathesis/runner/impl/core.py +0 -1246
- 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/loaders.py +0 -708
- 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/statistic.py +0 -22
- schemathesis/stateful/validation.py +0 -100
- schemathesis/targets.py +0 -77
- schemathesis/transports/__init__.py +0 -359
- 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.7.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.7.dist-info → schemathesis-4.0.0a2.dist-info}/WHEEL +0 -0
- {schemathesis-3.39.7.dist-info → schemathesis-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -3,29 +3,35 @@ from __future__ import annotations
|
|
3
3
|
import time
|
4
4
|
from contextlib import suppress
|
5
5
|
from dataclasses import dataclass
|
6
|
-
from typing import Any, Callable, Dict, Iterable, Optional
|
6
|
+
from typing import Any, Callable, Dict, Iterable, Optional, Union, cast
|
7
7
|
from urllib.parse import quote_plus
|
8
8
|
from weakref import WeakKeyDictionary
|
9
9
|
|
10
|
-
from hypothesis import reject
|
10
|
+
from hypothesis import event, note, reject
|
11
11
|
from hypothesis import strategies as st
|
12
12
|
from hypothesis_jsonschema import from_schema
|
13
|
-
|
14
|
-
from
|
15
|
-
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
19
|
-
from
|
20
|
-
|
13
|
+
|
14
|
+
from schemathesis.core import NOT_SET, NotSet, media_types
|
15
|
+
from schemathesis.core.control import SkipTest
|
16
|
+
from schemathesis.core.errors import SERIALIZERS_SUGGESTION_MESSAGE, SerializationNotPossible
|
17
|
+
from schemathesis.core.transforms import deepclone
|
18
|
+
from schemathesis.core.transport import prepare_urlencoded
|
19
|
+
from schemathesis.generation.meta import (
|
20
|
+
CaseMetadata,
|
21
|
+
ComponentInfo,
|
22
|
+
ComponentKind,
|
23
|
+
ExplicitPhaseData,
|
24
|
+
GeneratePhaseData,
|
25
|
+
GenerationInfo,
|
26
|
+
PhaseInfo,
|
27
|
+
TestPhase,
|
28
|
+
)
|
29
|
+
from schemathesis.openapi.generation.filters import is_valid_header, is_valid_path, is_valid_query, is_valid_urlencoded
|
30
|
+
from schemathesis.schemas import APIOperation
|
31
|
+
|
32
|
+
from ... import auths
|
33
|
+
from ...generation import GenerationConfig, GenerationMode
|
21
34
|
from ...hooks import HookContext, HookDispatcher, apply_to_all_dispatchers
|
22
|
-
from ...internal.copy import fast_deepcopy
|
23
|
-
from ...internal.validation import is_illegal_surrogate
|
24
|
-
from ...models import APIOperation, Case, GenerationMetadata, TestPhase, cant_serialize
|
25
|
-
from ...transports.content_types import parse_content_type
|
26
|
-
from ...transports.headers import has_invalid_characters, is_latin_1_encodable
|
27
|
-
from ...types import NotSet
|
28
|
-
from ...utils import skip
|
29
35
|
from .constants import LOCATION_TO_CONTAINER
|
30
36
|
from .formats import HEADER_FORMAT, STRING_FORMATS, get_default_format_strategies, header_values
|
31
37
|
from .media_types import MEDIA_TYPES
|
@@ -38,53 +44,21 @@ SLASH = "/"
|
|
38
44
|
StrategyFactory = Callable[[Dict[str, Any], str, str, Optional[str], GenerationConfig], st.SearchStrategy]
|
39
45
|
|
40
46
|
|
41
|
-
def is_valid_header(headers: dict[str, Any]) -> bool:
|
42
|
-
"""Verify if the generated headers are valid."""
|
43
|
-
for name, value in headers.items():
|
44
|
-
if not is_latin_1_encodable(value):
|
45
|
-
return False
|
46
|
-
if has_invalid_characters(name, value):
|
47
|
-
return False
|
48
|
-
return True
|
49
|
-
|
50
|
-
|
51
|
-
def is_valid_query(query: dict[str, Any]) -> bool:
|
52
|
-
"""Surrogates are not allowed in a query string.
|
53
|
-
|
54
|
-
`requests` and `werkzeug` will fail to send it to the application.
|
55
|
-
"""
|
56
|
-
for name, value in query.items():
|
57
|
-
if is_illegal_surrogate(name) or is_illegal_surrogate(value):
|
58
|
-
return False
|
59
|
-
return True
|
60
|
-
|
61
|
-
|
62
|
-
def is_valid_urlencoded(data: Any) -> bool:
|
63
|
-
if data is NOT_SET:
|
64
|
-
return True
|
65
|
-
try:
|
66
|
-
for _, __ in to_key_val_list(data): # type: ignore[no-untyped-call]
|
67
|
-
pass
|
68
|
-
return True
|
69
|
-
except (TypeError, ValueError):
|
70
|
-
return False
|
71
|
-
|
72
|
-
|
73
47
|
@st.composite # type: ignore
|
74
|
-
def
|
48
|
+
def openapi_cases(
|
75
49
|
draw: Callable,
|
50
|
+
*,
|
76
51
|
operation: APIOperation,
|
77
52
|
hooks: HookDispatcher | None = None,
|
78
53
|
auth_storage: auths.AuthStorage | None = None,
|
79
|
-
|
80
|
-
generation_config: GenerationConfig
|
54
|
+
generation_mode: GenerationMode = GenerationMode.default(),
|
55
|
+
generation_config: GenerationConfig,
|
81
56
|
path_parameters: NotSet | dict[str, Any] = NOT_SET,
|
82
57
|
headers: NotSet | dict[str, Any] = NOT_SET,
|
83
58
|
cookies: NotSet | dict[str, Any] = NOT_SET,
|
84
59
|
query: NotSet | dict[str, Any] = NOT_SET,
|
85
60
|
body: Any = NOT_SET,
|
86
61
|
media_type: str | None = None,
|
87
|
-
skip_on_not_negated: bool = True,
|
88
62
|
phase: TestPhase = TestPhase.GENERATE,
|
89
63
|
) -> Any:
|
90
64
|
"""A strategy that creates `Case` instances.
|
@@ -100,46 +74,59 @@ def get_case_strategy(
|
|
100
74
|
as it works with `body`.
|
101
75
|
"""
|
102
76
|
start = time.monotonic()
|
103
|
-
strategy_factory =
|
77
|
+
strategy_factory = GENERATOR_MODE_TO_STRATEGY_FACTORY[generation_mode]
|
104
78
|
|
105
79
|
context = HookContext(operation)
|
106
80
|
|
107
|
-
generation_config = generation_config or operation.schema.generation_config
|
108
|
-
|
109
81
|
path_parameters_ = generate_parameter(
|
110
|
-
"path", path_parameters, operation, draw, context, hooks,
|
82
|
+
"path", path_parameters, operation, draw, context, hooks, generation_mode, generation_config
|
111
83
|
)
|
112
|
-
headers_ = generate_parameter(
|
113
|
-
|
114
|
-
|
84
|
+
headers_ = generate_parameter(
|
85
|
+
"header", headers, operation, draw, context, hooks, generation_mode, generation_config
|
86
|
+
)
|
87
|
+
cookies_ = generate_parameter(
|
88
|
+
"cookie", cookies, operation, draw, context, hooks, generation_mode, generation_config
|
89
|
+
)
|
90
|
+
query_ = generate_parameter("query", query, operation, draw, context, hooks, generation_mode, generation_config)
|
115
91
|
|
116
92
|
if body is NOT_SET:
|
117
93
|
if operation.body:
|
118
|
-
body_generator =
|
119
|
-
if
|
94
|
+
body_generator = generation_mode
|
95
|
+
if generation_mode.is_negative:
|
120
96
|
# Consider only schemas that are possible to negate
|
121
97
|
candidates = [item for item in operation.body.items if can_negate(item.as_json_schema(operation))]
|
122
98
|
# Not possible to negate body, fallback to positive data generation
|
123
99
|
if not candidates:
|
124
100
|
candidates = operation.body.items
|
125
101
|
strategy_factory = make_positive_strategy
|
126
|
-
body_generator =
|
102
|
+
body_generator = GenerationMode.POSITIVE
|
127
103
|
else:
|
128
104
|
candidates = operation.body.items
|
129
105
|
parameter = draw(st.sampled_from(candidates))
|
130
106
|
strategy = _get_body_strategy(parameter, strategy_factory, operation, generation_config)
|
131
107
|
strategy = apply_hooks(operation, context, hooks, strategy, "body")
|
132
108
|
# Parameter may have a wildcard media type. In this case, choose any supported one
|
133
|
-
possible_media_types = sorted(
|
109
|
+
possible_media_types = sorted(
|
110
|
+
operation.schema.transport.get_matching_media_types(parameter.media_type), key=lambda x: x[0]
|
111
|
+
)
|
134
112
|
if not possible_media_types:
|
135
113
|
all_media_types = operation.get_request_payload_content_types()
|
136
|
-
if all(
|
114
|
+
if all(
|
115
|
+
operation.schema.transport.get_first_matching_media_type(media_type) is None
|
116
|
+
for media_type in all_media_types
|
117
|
+
):
|
137
118
|
# None of media types defined for this operation are not supported
|
138
119
|
raise SerializationNotPossible.from_media_types(*all_media_types)
|
139
120
|
# Other media types are possible - avoid choosing this media type in the future
|
140
|
-
|
141
|
-
|
142
|
-
|
121
|
+
event_text = f"Can't serialize data to `{parameter.media_type}`."
|
122
|
+
note(f"{event_text} {SERIALIZERS_SUGGESTION_MESSAGE}")
|
123
|
+
event(event_text)
|
124
|
+
reject() # type: ignore
|
125
|
+
media_type, _ = draw(st.sampled_from(possible_media_types))
|
126
|
+
if media_type is not None and media_types.parse(media_type) == (
|
127
|
+
"application",
|
128
|
+
"x-www-form-urlencoded",
|
129
|
+
):
|
143
130
|
strategy = strategy.map(prepare_urlencoded).filter(is_valid_urlencoded)
|
144
131
|
body_ = ValueContainer(value=draw(strategy), location="body", generator=body_generator)
|
145
132
|
else:
|
@@ -152,35 +139,43 @@ def get_case_strategy(
|
|
152
139
|
raise SerializationNotPossible.from_media_types(*all_media_types)
|
153
140
|
body_ = ValueContainer(value=body, location="body", generator=None)
|
154
141
|
|
155
|
-
if operation.schema.validate_schema and operation.method.upper() == "GET" and operation.body:
|
156
|
-
raise BodyInGetRequestError("GET requests should not contain body parameters.")
|
157
142
|
# If we need to generate negative cases but no generated values were negated, then skip the whole test
|
158
|
-
if
|
159
|
-
if
|
160
|
-
|
143
|
+
if generation_mode.is_negative and not any_negated_values([query_, cookies_, headers_, path_parameters_, body_]):
|
144
|
+
if generation_config.modes == [GenerationMode.NEGATIVE]:
|
145
|
+
raise SkipTest("Impossible to generate negative test cases")
|
161
146
|
else:
|
162
147
|
reject()
|
163
|
-
|
164
|
-
|
165
|
-
|
148
|
+
|
149
|
+
_phase_data = {
|
150
|
+
TestPhase.EXPLICIT: ExplicitPhaseData(),
|
151
|
+
TestPhase.GENERATE: GeneratePhaseData(),
|
152
|
+
}[phase]
|
153
|
+
phase_data = cast(Union[ExplicitPhaseData, GeneratePhaseData], _phase_data)
|
154
|
+
|
155
|
+
instance = operation.Case(
|
166
156
|
media_type=media_type,
|
167
157
|
path_parameters=path_parameters_.value,
|
168
|
-
headers=
|
158
|
+
headers=headers_.value,
|
169
159
|
cookies=cookies_.value,
|
170
160
|
query=query_.value,
|
171
161
|
body=body_.value,
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
162
|
+
meta=CaseMetadata(
|
163
|
+
generation=GenerationInfo(
|
164
|
+
time=time.monotonic() - start,
|
165
|
+
mode=generation_mode,
|
166
|
+
),
|
167
|
+
phase=PhaseInfo(name=phase, data=phase_data),
|
168
|
+
components={
|
169
|
+
kind: ComponentInfo(mode=value.generator)
|
170
|
+
for kind, value in [
|
171
|
+
(ComponentKind.QUERY, query_),
|
172
|
+
(ComponentKind.PATH_PARAMETERS, path_parameters_),
|
173
|
+
(ComponentKind.HEADERS, headers_),
|
174
|
+
(ComponentKind.COOKIES, cookies_),
|
175
|
+
(ComponentKind.BODY, body_),
|
176
|
+
]
|
177
|
+
if value.generator is not None
|
178
|
+
},
|
184
179
|
),
|
185
180
|
)
|
186
181
|
auth_context = auths.AuthContext(
|
@@ -208,7 +203,7 @@ def _get_body_strategy(
|
|
208
203
|
return _BODY_STRATEGIES_CACHE[parameter][strategy_factory]
|
209
204
|
schema = parameter.as_json_schema(operation)
|
210
205
|
schema = operation.schema.prepare_schema(schema)
|
211
|
-
strategy = strategy_factory(schema, operation.
|
206
|
+
strategy = strategy_factory(schema, operation.label, "body", parameter.media_type, generation_config)
|
212
207
|
if not parameter.is_required:
|
213
208
|
strategy |= st.just(NOT_SET)
|
214
209
|
_BODY_STRATEGIES_CACHE.setdefault(parameter, {})[strategy_factory] = strategy
|
@@ -238,7 +233,7 @@ def get_parameters_value(
|
|
238
233
|
strategy = apply_hooks(operation, context, hooks, strategy, location)
|
239
234
|
new = draw(strategy)
|
240
235
|
if new is not None:
|
241
|
-
copied =
|
236
|
+
copied = deepclone(value)
|
242
237
|
copied.update(new)
|
243
238
|
return copied
|
244
239
|
return value
|
@@ -253,7 +248,7 @@ class ValueContainer:
|
|
253
248
|
|
254
249
|
value: Any
|
255
250
|
location: str
|
256
|
-
generator:
|
251
|
+
generator: GenerationMode | None
|
257
252
|
|
258
253
|
__slots__ = ("value", "location", "generator")
|
259
254
|
|
@@ -265,7 +260,7 @@ class ValueContainer:
|
|
265
260
|
|
266
261
|
def any_negated_values(values: list[ValueContainer]) -> bool:
|
267
262
|
"""Check if any generated values are negated."""
|
268
|
-
return any(value.generator ==
|
263
|
+
return any(value.generator == GenerationMode.NEGATIVE for value in values if value.is_generated)
|
269
264
|
|
270
265
|
|
271
266
|
def generate_parameter(
|
@@ -275,7 +270,7 @@ def generate_parameter(
|
|
275
270
|
draw: Callable,
|
276
271
|
context: HookContext,
|
277
272
|
hooks: HookDispatcher | None,
|
278
|
-
generator:
|
273
|
+
generator: GenerationMode,
|
279
274
|
generation_config: GenerationConfig,
|
280
275
|
) -> ValueContainer:
|
281
276
|
"""Generate a value for a parameter.
|
@@ -289,13 +284,13 @@ def generate_parameter(
|
|
289
284
|
# If we can't negate any parameter, generate positive ones
|
290
285
|
# If nothing else will be negated, then skip the test completely
|
291
286
|
strategy_factory = make_positive_strategy
|
292
|
-
generator =
|
287
|
+
generator = GenerationMode.POSITIVE
|
293
288
|
else:
|
294
|
-
strategy_factory =
|
289
|
+
strategy_factory = GENERATOR_MODE_TO_STRATEGY_FACTORY[generator]
|
295
290
|
value = get_parameters_value(
|
296
291
|
explicit, location, draw, operation, context, hooks, strategy_factory, generation_config
|
297
292
|
)
|
298
|
-
used_generator:
|
293
|
+
used_generator: GenerationMode | None = generator
|
299
294
|
if value == explicit:
|
300
295
|
# When we pass `explicit`, then its parts are excluded from generation of the final value
|
301
296
|
# If the final value is the same, then other parameters were generated at all
|
@@ -329,11 +324,7 @@ def get_schema_for_location(
|
|
329
324
|
) -> dict[str, Any]:
|
330
325
|
schema = parameters_to_json_schema(operation, parameters)
|
331
326
|
if location == "path":
|
332
|
-
|
333
|
-
# If schema validation is disabled, we try to generate data even if the parameter definition
|
334
|
-
# contains errors.
|
335
|
-
# In this case, we know that the `required` keyword should always be `True`.
|
336
|
-
schema["required"] = list(schema["properties"])
|
327
|
+
schema["required"] = list(schema["properties"])
|
337
328
|
for prop in schema.get("properties", {}).values():
|
338
329
|
if prop.get("type") == "string":
|
339
330
|
prop.setdefault("minLength", 1)
|
@@ -365,7 +356,7 @@ def get_parameters_strategy(
|
|
365
356
|
# Nothing to negate - all properties were excluded
|
366
357
|
strategy = st.none()
|
367
358
|
else:
|
368
|
-
strategy = strategy_factory(schema, operation.
|
359
|
+
strategy = strategy_factory(schema, operation.label, location, None, generation_config)
|
369
360
|
serialize = operation.get_parameter_serializer(location)
|
370
361
|
if serialize is not None:
|
371
362
|
strategy = strategy.map(serialize)
|
@@ -472,35 +463,12 @@ def make_negative_strategy(
|
|
472
463
|
)
|
473
464
|
|
474
465
|
|
475
|
-
|
476
|
-
|
477
|
-
|
466
|
+
GENERATOR_MODE_TO_STRATEGY_FACTORY = {
|
467
|
+
GenerationMode.POSITIVE: make_positive_strategy,
|
468
|
+
GenerationMode.NEGATIVE: make_negative_strategy,
|
478
469
|
}
|
479
470
|
|
480
471
|
|
481
|
-
def is_valid_path(parameters: dict[str, Any]) -> bool:
|
482
|
-
"""Empty strings ("") are excluded from path by urllib3.
|
483
|
-
|
484
|
-
A path containing to "/" or "%2F" will lead to ambiguous path resolution in
|
485
|
-
many frameworks and libraries, such behaviour have been observed in both
|
486
|
-
WSGI and ASGI applications.
|
487
|
-
|
488
|
-
In this case one variable in the path template will be empty, which will lead to 404 in most of the cases.
|
489
|
-
Because of it this case doesn't bring much value and might lead to false positives results of Schemathesis runs.
|
490
|
-
"""
|
491
|
-
disallowed_values = (SLASH, "")
|
492
|
-
|
493
|
-
return not any(
|
494
|
-
(
|
495
|
-
value in disallowed_values
|
496
|
-
or is_illegal_surrogate(value)
|
497
|
-
or isinstance(value, str)
|
498
|
-
and (SLASH in value or "}" in value or "{" in value)
|
499
|
-
)
|
500
|
-
for value in parameters.values()
|
501
|
-
)
|
502
|
-
|
503
|
-
|
504
472
|
def quote_all(parameters: dict[str, Any]) -> dict[str, Any]:
|
505
473
|
"""Apply URL quotation for all values in a dictionary."""
|
506
474
|
# Even though, "." is an unreserved character, it has a special meaning in "." and ".." strings.
|
@@ -530,8 +498,3 @@ def apply_hooks(
|
|
530
498
|
"""Apply all hooks related to the given location."""
|
531
499
|
container = LOCATION_TO_CONTAINER[location]
|
532
500
|
return apply_to_all_dispatchers(operation, context, hooks, strategy, container)
|
533
|
-
|
534
|
-
|
535
|
-
def clear_cache() -> None:
|
536
|
-
_PARAMETER_STRATEGIES_CACHE.clear()
|
537
|
-
_BODY_STRATEGIES_CACHE.clear()
|