schemathesis 4.3.3__py3-none-any.whl → 4.3.5__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.
Potentially problematic release.
This version of schemathesis might be problematic. Click here for more details.
- schemathesis/cli/commands/run/context.py +27 -3
- schemathesis/cli/commands/run/handlers/output.py +4 -3
- schemathesis/core/jsonschema/bundler.py +18 -10
- schemathesis/engine/context.py +1 -25
- schemathesis/generation/hypothesis/builder.py +0 -1
- schemathesis/generation/stateful/state_machine.py +2 -1
- schemathesis/specs/openapi/_hypothesis.py +14 -5
- schemathesis/specs/openapi/stateful/dependencies/__init__.py +123 -1
- schemathesis/specs/openapi/stateful/dependencies/inputs.py +68 -21
- schemathesis/specs/openapi/stateful/dependencies/models.py +24 -4
- schemathesis/specs/openapi/stateful/dependencies/resources.py +6 -2
- schemathesis/specs/openapi/stateful/dependencies/schemas.py +21 -10
- schemathesis/specs/openapi/stateful/inference.py +3 -2
- schemathesis/specs/openapi/stateful/links.py +15 -2
- {schemathesis-4.3.3.dist-info → schemathesis-4.3.5.dist-info}/METADATA +1 -1
- {schemathesis-4.3.3.dist-info → schemathesis-4.3.5.dist-info}/RECORD +19 -19
- {schemathesis-4.3.3.dist-info → schemathesis-4.3.5.dist-info}/WHEEL +0 -0
- {schemathesis-4.3.3.dist-info → schemathesis-4.3.5.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.3.3.dist-info → schemathesis-4.3.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
5
|
from typing import TYPE_CHECKING, Callable, Generator
|
|
5
6
|
|
|
@@ -112,7 +113,8 @@ class Statistic:
|
|
|
112
113
|
if has_failures:
|
|
113
114
|
self.cases_with_failures += 1
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
# Don't report extraction failures for inferred transitions
|
|
117
|
+
if case.transition is None or case.transition.is_inferred:
|
|
116
118
|
continue
|
|
117
119
|
transition = case.transition
|
|
118
120
|
parent = recorder.cases[transition.parent_id]
|
|
@@ -120,10 +122,32 @@ class Statistic:
|
|
|
120
122
|
# We need a response to get there, so it should be present
|
|
121
123
|
assert response is not None
|
|
122
124
|
|
|
125
|
+
history = None
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
transition.request_body is not None
|
|
129
|
+
and isinstance(transition.request_body.value, Ok)
|
|
130
|
+
and transition.request_body.value.ok() is UNRESOLVABLE
|
|
131
|
+
):
|
|
132
|
+
history = collect_history(parent, response)
|
|
133
|
+
extraction_failures.add(
|
|
134
|
+
ExtractionFailure(
|
|
135
|
+
id=transition.id,
|
|
136
|
+
case_id=case_id,
|
|
137
|
+
source=parent.value.operation.label,
|
|
138
|
+
target=case.value.operation.label,
|
|
139
|
+
parameter_name="body",
|
|
140
|
+
expression=json.dumps(transition.request_body.definition),
|
|
141
|
+
history=history,
|
|
142
|
+
response=response,
|
|
143
|
+
error=None,
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
|
|
123
147
|
for params in transition.parameters.values():
|
|
124
148
|
for parameter, extracted in params.items():
|
|
125
149
|
if isinstance(extracted.value, Ok) and extracted.value.ok() is UNRESOLVABLE:
|
|
126
|
-
history = collect_history(parent, response)
|
|
150
|
+
history = history or collect_history(parent, response)
|
|
127
151
|
extraction_failures.add(
|
|
128
152
|
ExtractionFailure(
|
|
129
153
|
id=transition.id,
|
|
@@ -138,7 +162,7 @@ class Statistic:
|
|
|
138
162
|
)
|
|
139
163
|
)
|
|
140
164
|
elif isinstance(extracted.value, Err):
|
|
141
|
-
history = collect_history(parent, response)
|
|
165
|
+
history = history or collect_history(parent, response)
|
|
142
166
|
extraction_failures.add(
|
|
143
167
|
ExtractionFailure(
|
|
144
168
|
id=transition.id,
|
|
@@ -1295,9 +1295,10 @@ class OutputHandler(EventHandler):
|
|
|
1295
1295
|
else:
|
|
1296
1296
|
click.echo(f"\n{indent}{failure.error.__class__.__name__}: {failure.error}")
|
|
1297
1297
|
else:
|
|
1298
|
-
|
|
1299
|
-
f"\n{indent}Could not resolve
|
|
1300
|
-
|
|
1298
|
+
if failure.parameter_name == "body":
|
|
1299
|
+
description = f"\n{indent}Could not resolve request body via {failure.expression}"
|
|
1300
|
+
else:
|
|
1301
|
+
description = f"\n{indent}Could not resolve parameter `{failure.parameter_name}` via `{failure.expression}`"
|
|
1301
1302
|
prefix = "$response.body"
|
|
1302
1303
|
if failure.expression.startswith(prefix):
|
|
1303
1304
|
description += f"\n{indent}Path `{failure.expression[len(prefix) :]}` not found in response"
|
|
@@ -41,6 +41,7 @@ class Bundler:
|
|
|
41
41
|
return schema
|
|
42
42
|
|
|
43
43
|
# Track visited URIs and their local definition names
|
|
44
|
+
inlining_for_recursion: set[str] = set()
|
|
44
45
|
visited: set[str] = set()
|
|
45
46
|
uri_to_def_name: dict[str, str] = {}
|
|
46
47
|
defs = {}
|
|
@@ -90,19 +91,26 @@ class Bundler:
|
|
|
90
91
|
# L is the number of levels. Even quadratic growth can be unacceptable for large schemas.
|
|
91
92
|
#
|
|
92
93
|
# In the future, it **should** be handled by `hypothesis-jsonschema` instead.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# This schema is either infinitely recursive or the sanitization logic misses it
|
|
94
|
+
if resolved_uri in inlining_for_recursion:
|
|
95
|
+
# Check if we're already trying to inline this schema
|
|
96
|
+
# If yes, it means we have an unbreakable cycle
|
|
97
97
|
cycle = scopes[scopes.index(resolved_uri) :]
|
|
98
98
|
raise InfiniteRecursiveReference(reference, cycle)
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
# Track that we're inlining this schema
|
|
101
|
+
inlining_for_recursion.add(resolved_uri)
|
|
102
|
+
try:
|
|
103
|
+
cloned = deepclone(resolved_schema)
|
|
104
|
+
# Sanitize to remove optional recursive references
|
|
105
|
+
sanitize(cloned)
|
|
106
|
+
|
|
107
|
+
result = {key: _bundle_recursive(value) for key, value in current.items() if key != "$ref"}
|
|
108
|
+
bundled_clone = _bundle_recursive(cloned)
|
|
109
|
+
assert isinstance(bundled_clone, dict)
|
|
110
|
+
result.update(bundled_clone)
|
|
111
|
+
return result
|
|
112
|
+
finally:
|
|
113
|
+
inlining_for_recursion.discard(resolved_uri)
|
|
106
114
|
elif resolved_uri not in visited:
|
|
107
115
|
# Bundle only new schemas
|
|
108
116
|
visit(resolved_uri)
|
schemathesis/engine/context.py
CHANGED
|
@@ -105,18 +105,7 @@ class EngineContext:
|
|
|
105
105
|
InferenceAlgorithm.DEPENDENCY_ANALYSIS
|
|
106
106
|
)
|
|
107
107
|
):
|
|
108
|
-
|
|
109
|
-
for response_links in graph.iter_links():
|
|
110
|
-
operation = self.schema.get_operation_by_reference(response_links.producer_operation_ref)
|
|
111
|
-
response = operation.responses.get(response_links.status_code)
|
|
112
|
-
links = response.definition.setdefault(self.schema.adapter.links_keyword, {})
|
|
113
|
-
|
|
114
|
-
for link_name, definition in response_links.links.items():
|
|
115
|
-
# Find unique name if collision exists
|
|
116
|
-
final_name = _resolve_link_name_collision(link_name, links)
|
|
117
|
-
links[final_name] = definition.to_openapi()
|
|
118
|
-
injected += 1
|
|
119
|
-
|
|
108
|
+
injected += dependencies.inject_links(self.schema)
|
|
120
109
|
return injected
|
|
121
110
|
|
|
122
111
|
def stop(self) -> None:
|
|
@@ -171,16 +160,3 @@ class EngineContext:
|
|
|
171
160
|
kwargs["proxies"] = {"all": proxy}
|
|
172
161
|
self._transport_kwargs_cache[key] = kwargs
|
|
173
162
|
return kwargs
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def _resolve_link_name_collision(proposed_name: str, existing_links: dict[str, Any]) -> str:
|
|
177
|
-
if proposed_name not in existing_links:
|
|
178
|
-
return proposed_name
|
|
179
|
-
|
|
180
|
-
# Name collision - find next available suffix
|
|
181
|
-
suffix = 0
|
|
182
|
-
while True:
|
|
183
|
-
candidate = f"{proposed_name}_{suffix}"
|
|
184
|
-
if candidate not in existing_links:
|
|
185
|
-
return candidate
|
|
186
|
-
suffix += 1
|
|
@@ -530,7 +530,6 @@ def _iter_coverage_cases(
|
|
|
530
530
|
instant = Instant()
|
|
531
531
|
responses = list(operation.responses.iter_examples())
|
|
532
532
|
# NOTE: The HEAD method is excluded
|
|
533
|
-
unexpected_methods = unexpected_methods or {"get", "put", "post", "delete", "options", "patch", "trace"}
|
|
534
533
|
custom_formats = _build_custom_formats(generation_config)
|
|
535
534
|
|
|
536
535
|
seen_negative = coverage.HashSet()
|
|
@@ -56,10 +56,11 @@ class Transition:
|
|
|
56
56
|
# ID of the transition (e.g. link name)
|
|
57
57
|
id: str
|
|
58
58
|
parent_id: str
|
|
59
|
+
is_inferred: bool
|
|
59
60
|
parameters: dict[str, dict[str, ExtractedParam]]
|
|
60
61
|
request_body: ExtractedParam | None
|
|
61
62
|
|
|
62
|
-
__slots__ = ("id", "parent_id", "parameters", "request_body")
|
|
63
|
+
__slots__ = ("id", "parent_id", "is_inferred", "parameters", "request_body")
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
@dataclass
|
|
@@ -56,7 +56,7 @@ StrategyFactory = Callable[
|
|
|
56
56
|
|
|
57
57
|
@st.composite # type: ignore
|
|
58
58
|
def openapi_cases(
|
|
59
|
-
draw:
|
|
59
|
+
draw: st.DrawFn,
|
|
60
60
|
*,
|
|
61
61
|
operation: APIOperation,
|
|
62
62
|
hooks: HookDispatcher | None = None,
|
|
@@ -116,7 +116,7 @@ def openapi_cases(
|
|
|
116
116
|
else:
|
|
117
117
|
candidates = operation.body.items
|
|
118
118
|
parameter = draw(st.sampled_from(candidates))
|
|
119
|
-
strategy = _get_body_strategy(parameter, strategy_factory, operation, generation_config)
|
|
119
|
+
strategy = _get_body_strategy(parameter, strategy_factory, operation, generation_config, draw)
|
|
120
120
|
strategy = apply_hooks(operation, ctx, hooks, strategy, ParameterLocation.BODY)
|
|
121
121
|
# Parameter may have a wildcard media type. In this case, choose any supported one
|
|
122
122
|
possible_media_types = sorted(
|
|
@@ -200,11 +200,15 @@ def openapi_cases(
|
|
|
200
200
|
return instance
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
OPTIONAL_BODY_RATE = 0.05
|
|
204
|
+
|
|
205
|
+
|
|
203
206
|
def _get_body_strategy(
|
|
204
207
|
parameter: OpenApiBody,
|
|
205
208
|
strategy_factory: StrategyFactory,
|
|
206
209
|
operation: APIOperation,
|
|
207
210
|
generation_config: GenerationConfig,
|
|
211
|
+
draw: st.DrawFn,
|
|
208
212
|
) -> st.SearchStrategy:
|
|
209
213
|
from schemathesis.specs.openapi.schemas import BaseOpenAPISchema
|
|
210
214
|
|
|
@@ -220,7 +224,12 @@ def _get_body_strategy(
|
|
|
220
224
|
generation_config,
|
|
221
225
|
operation.schema.adapter.jsonschema_validator_cls,
|
|
222
226
|
)
|
|
223
|
-
|
|
227
|
+
# It is likely will be rejected, hence choose it rarely
|
|
228
|
+
if (
|
|
229
|
+
not parameter.is_required
|
|
230
|
+
and draw(st.floats(min_value=0.0, max_value=1.0, allow_infinity=False, allow_nan=False, allow_subnormal=False))
|
|
231
|
+
< OPTIONAL_BODY_RATE
|
|
232
|
+
):
|
|
224
233
|
strategy |= st.just(NOT_SET)
|
|
225
234
|
return strategy
|
|
226
235
|
|
|
@@ -228,7 +237,7 @@ def _get_body_strategy(
|
|
|
228
237
|
def get_parameters_value(
|
|
229
238
|
value: dict[str, Any] | None,
|
|
230
239
|
location: ParameterLocation,
|
|
231
|
-
draw:
|
|
240
|
+
draw: st.DrawFn,
|
|
232
241
|
operation: APIOperation,
|
|
233
242
|
ctx: HookContext,
|
|
234
243
|
hooks: HookDispatcher | None,
|
|
@@ -279,7 +288,7 @@ def generate_parameter(
|
|
|
279
288
|
location: ParameterLocation,
|
|
280
289
|
explicit: dict[str, Any] | None,
|
|
281
290
|
operation: APIOperation,
|
|
282
|
-
draw:
|
|
291
|
+
draw: st.DrawFn,
|
|
283
292
|
ctx: HookContext,
|
|
284
293
|
hooks: HookDispatcher | None,
|
|
285
294
|
generator: GenerationMode,
|
|
@@ -5,8 +5,9 @@ Infers which operations must run before others by tracking resource creation and
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
9
|
|
|
10
|
+
from schemathesis.core import NOT_SET
|
|
10
11
|
from schemathesis.core.compat import RefResolutionError
|
|
11
12
|
from schemathesis.core.result import Ok
|
|
12
13
|
from schemathesis.specs.openapi.stateful.dependencies.inputs import extract_inputs, update_input_field_bindings
|
|
@@ -16,6 +17,7 @@ from schemathesis.specs.openapi.stateful.dependencies.models import (
|
|
|
16
17
|
DefinitionSource,
|
|
17
18
|
DependencyGraph,
|
|
18
19
|
InputSlot,
|
|
20
|
+
NormalizedLink,
|
|
19
21
|
OperationMap,
|
|
20
22
|
OperationNode,
|
|
21
23
|
OutputSlot,
|
|
@@ -25,6 +27,7 @@ from schemathesis.specs.openapi.stateful.dependencies.models import (
|
|
|
25
27
|
from schemathesis.specs.openapi.stateful.dependencies.outputs import extract_outputs
|
|
26
28
|
|
|
27
29
|
if TYPE_CHECKING:
|
|
30
|
+
from schemathesis.schemas import APIOperation
|
|
28
31
|
from schemathesis.specs.openapi.schemas import BaseOpenAPISchema
|
|
29
32
|
|
|
30
33
|
__all__ = [
|
|
@@ -86,3 +89,122 @@ def analyze(schema: BaseOpenAPISchema) -> DependencyGraph:
|
|
|
86
89
|
update_input_field_bindings(resource, operations)
|
|
87
90
|
|
|
88
91
|
return DependencyGraph(operations=operations, resources=resources)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def inject_links(schema: BaseOpenAPISchema) -> int:
|
|
95
|
+
injected = 0
|
|
96
|
+
graph = analyze(schema)
|
|
97
|
+
for response_links in graph.iter_links():
|
|
98
|
+
operation = schema.get_operation_by_reference(response_links.producer_operation_ref)
|
|
99
|
+
response = operation.responses.get(response_links.status_code)
|
|
100
|
+
links = response.definition.setdefault(schema.adapter.links_keyword, {})
|
|
101
|
+
|
|
102
|
+
# Normalize existing links once
|
|
103
|
+
if links:
|
|
104
|
+
normalized_existing = [_normalize_link(link, schema) for link in links.values()]
|
|
105
|
+
else:
|
|
106
|
+
normalized_existing = []
|
|
107
|
+
|
|
108
|
+
for link_name, definition in response_links.links.items():
|
|
109
|
+
inferred_link = definition.to_openapi()
|
|
110
|
+
|
|
111
|
+
# Check if duplicate / subsets exists
|
|
112
|
+
if normalized_existing:
|
|
113
|
+
normalized = _normalize_link(inferred_link, schema)
|
|
114
|
+
if any(_is_subset_link(normalized, existing) for existing in normalized_existing):
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
# Find unique name if collision exists
|
|
118
|
+
final_name = _resolve_link_name_collision(link_name, links)
|
|
119
|
+
links[final_name] = inferred_link
|
|
120
|
+
injected += 1
|
|
121
|
+
return injected
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _normalize_link(link: dict[str, Any], schema: BaseOpenAPISchema) -> NormalizedLink:
|
|
125
|
+
"""Normalize a link definition for comparison."""
|
|
126
|
+
operation = _resolve_link_operation(link, schema)
|
|
127
|
+
|
|
128
|
+
normalized_params = _normalize_parameter_keys(link.get("parameters", {}), operation)
|
|
129
|
+
|
|
130
|
+
return NormalizedLink(
|
|
131
|
+
path=operation.path,
|
|
132
|
+
method=operation.method,
|
|
133
|
+
parameters=normalized_params,
|
|
134
|
+
request_body=link.get("requestBody", {}),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _normalize_parameter_keys(parameters: dict, operation: APIOperation) -> set[str]:
|
|
139
|
+
"""Normalize parameter keys to location.name format."""
|
|
140
|
+
normalized = set()
|
|
141
|
+
|
|
142
|
+
for parameter_name in parameters.keys():
|
|
143
|
+
# If already has location prefix, use as-is
|
|
144
|
+
if "." in parameter_name:
|
|
145
|
+
normalized.add(parameter_name)
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
# Find the parameter and prepend location
|
|
149
|
+
for parameter in operation.iter_parameters():
|
|
150
|
+
if parameter.name == parameter_name:
|
|
151
|
+
normalized.add(f"{parameter.location.value}.{parameter_name}")
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
return normalized
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _resolve_link_operation(link: dict, schema: BaseOpenAPISchema) -> APIOperation:
|
|
158
|
+
"""Resolve link to operation, handling both operationRef and operationId."""
|
|
159
|
+
if "operationRef" in link:
|
|
160
|
+
return schema.get_operation_by_reference(link["operationRef"])
|
|
161
|
+
return schema.get_operation_by_id(link["operationId"])
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _resolve_link_name_collision(proposed_name: str, existing_links: dict[str, Any]) -> str:
|
|
165
|
+
"""Find unique link name if collision exists."""
|
|
166
|
+
if proposed_name not in existing_links:
|
|
167
|
+
return proposed_name
|
|
168
|
+
|
|
169
|
+
suffix = 0
|
|
170
|
+
while True:
|
|
171
|
+
candidate = f"{proposed_name}_{suffix}"
|
|
172
|
+
if candidate not in existing_links:
|
|
173
|
+
return candidate
|
|
174
|
+
suffix += 1
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _is_subset_link(inferred: NormalizedLink, existing: NormalizedLink) -> bool:
|
|
178
|
+
"""Check if inferred link is a subset of existing link."""
|
|
179
|
+
# Must target the same operation
|
|
180
|
+
if inferred.path != existing.path or inferred.method != existing.method:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
# Inferred parameters must be subset of existing parameters
|
|
184
|
+
if not inferred.parameters.issubset(existing.parameters):
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
# Inferred request body must be subset of existing body
|
|
188
|
+
return _is_request_body_subset(inferred.request_body, existing.request_body)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _is_request_body_subset(inferred_body: Any, existing_body: Any) -> bool:
|
|
192
|
+
"""Check if inferred body is a subset of existing body."""
|
|
193
|
+
# Empty inferred body is always a subset
|
|
194
|
+
if not inferred_body:
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
# If existing is empty but inferred isn't, not a subset
|
|
198
|
+
if not existing_body:
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
# Both must be dicts for subset comparison, otherwise check for equality
|
|
202
|
+
if not isinstance(inferred_body, dict) or not isinstance(existing_body, dict):
|
|
203
|
+
return inferred_body == existing_body
|
|
204
|
+
|
|
205
|
+
# Check if all inferred fields exist in existing with same values
|
|
206
|
+
for key, value in inferred_body.items():
|
|
207
|
+
if existing_body.get(key, NOT_SET) != value:
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
return True
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Iterator
|
|
|
5
5
|
from schemathesis.core import media_types
|
|
6
6
|
from schemathesis.core.errors import MalformedMediaType
|
|
7
7
|
from schemathesis.core.jsonschema.bundler import BUNDLE_STORAGE_KEY
|
|
8
|
+
from schemathesis.core.jsonschema.types import get_type
|
|
8
9
|
from schemathesis.core.parameters import ParameterLocation
|
|
9
10
|
from schemathesis.specs.openapi.stateful.dependencies import naming
|
|
10
11
|
from schemathesis.specs.openapi.stateful.dependencies.models import (
|
|
@@ -36,6 +37,7 @@ def extract_inputs(
|
|
|
36
37
|
Connects each parameter (e.g., `userId`) to its resource definition (`User`),
|
|
37
38
|
creating placeholder resources if not yet discovered from their schemas.
|
|
38
39
|
"""
|
|
40
|
+
known_dependencies = set()
|
|
39
41
|
for param in operation.path_parameters:
|
|
40
42
|
input_slot = _resolve_parameter_dependency(
|
|
41
43
|
parameter_name=param.name,
|
|
@@ -47,12 +49,16 @@ def extract_inputs(
|
|
|
47
49
|
canonicalization_cache=canonicalization_cache,
|
|
48
50
|
)
|
|
49
51
|
if input_slot is not None:
|
|
52
|
+
if input_slot.resource.source >= DefinitionSource.SCHEMA_WITH_PROPERTIES:
|
|
53
|
+
known_dependencies.add(input_slot.resource.name)
|
|
50
54
|
yield input_slot
|
|
51
55
|
|
|
52
56
|
for body in operation.body:
|
|
53
57
|
try:
|
|
54
58
|
if media_types.is_json(body.media_type):
|
|
55
|
-
yield from _resolve_body_dependencies(
|
|
59
|
+
yield from _resolve_body_dependencies(
|
|
60
|
+
body=body, operation=operation, resources=resources, known_dependencies=known_dependencies
|
|
61
|
+
)
|
|
56
62
|
except MalformedMediaType:
|
|
57
63
|
continue
|
|
58
64
|
|
|
@@ -162,8 +168,23 @@ def _find_resource_in_responses(
|
|
|
162
168
|
return None
|
|
163
169
|
|
|
164
170
|
|
|
171
|
+
GENERIC_FIELD_NAMES = frozenset(
|
|
172
|
+
{
|
|
173
|
+
"body",
|
|
174
|
+
"text",
|
|
175
|
+
"content",
|
|
176
|
+
"message",
|
|
177
|
+
"description",
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
165
182
|
def _resolve_body_dependencies(
|
|
166
|
-
*,
|
|
183
|
+
*,
|
|
184
|
+
body: OpenApiBody,
|
|
185
|
+
operation: APIOperation,
|
|
186
|
+
resources: ResourceMap,
|
|
187
|
+
known_dependencies: set[str],
|
|
167
188
|
) -> Iterator[InputSlot]:
|
|
168
189
|
schema = body.raw_schema
|
|
169
190
|
if not isinstance(schema, dict):
|
|
@@ -178,31 +199,57 @@ def _resolve_body_dependencies(
|
|
|
178
199
|
|
|
179
200
|
# Inspect each property that could be a part of some other resource
|
|
180
201
|
properties = resolved.get("properties", {})
|
|
202
|
+
required = resolved.get("required", [])
|
|
181
203
|
path = operation.path
|
|
182
|
-
for property_name in properties:
|
|
204
|
+
for property_name, subschema in properties.items():
|
|
183
205
|
resource_name = naming.from_parameter(property_name, path)
|
|
184
|
-
if resource_name is None:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
206
|
+
if resource_name is not None:
|
|
207
|
+
resource = resources.get(resource_name)
|
|
208
|
+
if resource is None:
|
|
209
|
+
resource = ResourceDefinition.inferred_from_parameter(
|
|
210
|
+
name=resource_name,
|
|
211
|
+
parameter_name=property_name,
|
|
212
|
+
)
|
|
213
|
+
resources[resource_name] = resource
|
|
214
|
+
field = property_name
|
|
215
|
+
else:
|
|
216
|
+
field = (
|
|
217
|
+
naming.find_matching_field(
|
|
218
|
+
parameter=property_name,
|
|
219
|
+
resource=resource_name,
|
|
220
|
+
fields=resource.fields,
|
|
221
|
+
)
|
|
222
|
+
or "id"
|
|
200
223
|
)
|
|
201
|
-
|
|
224
|
+
yield InputSlot(
|
|
225
|
+
resource=resource,
|
|
226
|
+
resource_field=field,
|
|
227
|
+
parameter_name=property_name,
|
|
228
|
+
parameter_location=ParameterLocation.BODY,
|
|
202
229
|
)
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
# Skip generic property names & optional fields (at least for now)
|
|
233
|
+
if property_name in GENERIC_FIELD_NAMES or property_name not in required:
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
# Find candidate resources among known dependencies that actually have this field
|
|
237
|
+
candidates = [
|
|
238
|
+
resources[dep] for dep in known_dependencies if dep in resources and property_name in resources[dep].fields
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
# Skip ambiguous cases when multiple resources have same field name
|
|
242
|
+
if len(candidates) != 1:
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
resource = candidates[0]
|
|
246
|
+
# Ensure the target field supports the same type
|
|
247
|
+
if not resource.types[property_name] & set(get_type(subschema)):
|
|
248
|
+
continue
|
|
249
|
+
|
|
203
250
|
yield InputSlot(
|
|
204
251
|
resource=resource,
|
|
205
|
-
resource_field=
|
|
252
|
+
resource_field=property_name,
|
|
206
253
|
parameter_name=property_name,
|
|
207
254
|
parameter_location=ParameterLocation.BODY,
|
|
208
255
|
)
|
|
@@ -73,6 +73,11 @@ class DependencyGraph:
|
|
|
73
73
|
parameters = {
|
|
74
74
|
f"{input_slot.parameter_location.value}.{input_slot.parameter_name}": f"$response.body#{body_pointer}",
|
|
75
75
|
}
|
|
76
|
+
existing = links.get(link_name)
|
|
77
|
+
if existing is not None:
|
|
78
|
+
existing.parameters.update(parameters)
|
|
79
|
+
existing.request_body.update(request_body)
|
|
80
|
+
continue
|
|
76
81
|
links[link_name] = LinkDefinition(
|
|
77
82
|
operation_ref=f"#/paths/{consumer_path}/{consumer.method}",
|
|
78
83
|
parameters=parameters,
|
|
@@ -155,12 +160,13 @@ class LinkDefinition:
|
|
|
155
160
|
"""Convert to OpenAPI Links format."""
|
|
156
161
|
links: dict[str, Any] = {
|
|
157
162
|
"operationRef": self.operation_ref,
|
|
163
|
+
SCHEMATHESIS_LINK_EXTENSION: {"is_inferred": True},
|
|
158
164
|
}
|
|
159
165
|
if self.parameters:
|
|
160
166
|
links["parameters"] = self.parameters
|
|
161
167
|
if self.request_body:
|
|
162
168
|
links["requestBody"] = self.request_body
|
|
163
|
-
links[SCHEMATHESIS_LINK_EXTENSION]
|
|
169
|
+
links[SCHEMATHESIS_LINK_EXTENSION]["merge_body"] = True
|
|
164
170
|
return links
|
|
165
171
|
|
|
166
172
|
|
|
@@ -195,6 +201,18 @@ class ResponseLinks:
|
|
|
195
201
|
return {name: link_def.to_openapi() for name, link_def in self.links.items()}
|
|
196
202
|
|
|
197
203
|
|
|
204
|
+
@dataclass
|
|
205
|
+
class NormalizedLink:
|
|
206
|
+
"""Normalized representation of a link."""
|
|
207
|
+
|
|
208
|
+
path: str
|
|
209
|
+
method: str
|
|
210
|
+
parameters: set[str]
|
|
211
|
+
request_body: Any
|
|
212
|
+
|
|
213
|
+
__slots__ = ("path", "method", "parameters", "request_body")
|
|
214
|
+
|
|
215
|
+
|
|
198
216
|
class Cardinality(str, enum.Enum):
|
|
199
217
|
"""Whether there is one or many resources in a slot."""
|
|
200
218
|
|
|
@@ -254,18 +272,20 @@ class ResourceDefinition:
|
|
|
254
272
|
name: str
|
|
255
273
|
# A sorted list of resource fields
|
|
256
274
|
fields: list[str]
|
|
275
|
+
# Field types mapping
|
|
276
|
+
types: dict[str, set[str]]
|
|
257
277
|
# How this resource was created
|
|
258
278
|
source: DefinitionSource
|
|
259
279
|
|
|
260
|
-
__slots__ = ("name", "fields", "source")
|
|
280
|
+
__slots__ = ("name", "fields", "types", "source")
|
|
261
281
|
|
|
262
282
|
@classmethod
|
|
263
283
|
def without_properties(cls, name: str) -> ResourceDefinition:
|
|
264
|
-
return cls(name=name, fields=[], source=DefinitionSource.SCHEMA_WITHOUT_PROPERTIES)
|
|
284
|
+
return cls(name=name, fields=[], types={}, source=DefinitionSource.SCHEMA_WITHOUT_PROPERTIES)
|
|
265
285
|
|
|
266
286
|
@classmethod
|
|
267
287
|
def inferred_from_parameter(cls, name: str, parameter_name: str) -> ResourceDefinition:
|
|
268
|
-
return cls(name=name, fields=[parameter_name], source=DefinitionSource.PARAMETER_INFERENCE)
|
|
288
|
+
return cls(name=name, fields=[parameter_name], types={}, source=DefinitionSource.PARAMETER_INFERENCE)
|
|
269
289
|
|
|
270
290
|
|
|
271
291
|
class DefinitionSource(enum.IntEnum):
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, Iterator, Mapping, cast
|
|
|
5
5
|
|
|
6
6
|
from schemathesis.core.errors import InfiniteRecursiveReference
|
|
7
7
|
from schemathesis.core.jsonschema.bundler import BundleError
|
|
8
|
+
from schemathesis.core.jsonschema.types import get_type
|
|
8
9
|
from schemathesis.specs.openapi.adapter.parameters import resource_name_from_ref
|
|
9
10
|
from schemathesis.specs.openapi.adapter.references import maybe_resolve
|
|
10
11
|
from schemathesis.specs.openapi.stateful.dependencies import naming
|
|
@@ -265,18 +266,21 @@ def _extract_resource_from_schema(
|
|
|
265
266
|
|
|
266
267
|
properties = resolved.get("properties")
|
|
267
268
|
if properties:
|
|
268
|
-
fields =
|
|
269
|
+
fields = sorted(properties)
|
|
270
|
+
types = {field: set(get_type(subschema)) for field, subschema in properties.items()}
|
|
269
271
|
source = DefinitionSource.SCHEMA_WITH_PROPERTIES
|
|
270
272
|
else:
|
|
271
273
|
fields = []
|
|
274
|
+
types = {}
|
|
272
275
|
source = DefinitionSource.SCHEMA_WITHOUT_PROPERTIES
|
|
273
276
|
if resource is not None:
|
|
274
277
|
if resource.source < source:
|
|
275
278
|
resource.source = source
|
|
276
279
|
resource.fields = fields
|
|
280
|
+
resource.types = types
|
|
277
281
|
updated_resources.add(resource_name)
|
|
278
282
|
else:
|
|
279
|
-
resource = ResourceDefinition(name=resource_name, fields=fields, source=source)
|
|
283
|
+
resource = ResourceDefinition(name=resource_name, fields=fields, types=types, source=source)
|
|
280
284
|
resources[resource_name] = resource
|
|
281
285
|
|
|
282
286
|
return resource
|
|
@@ -3,14 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Callable, Mapping
|
|
5
5
|
|
|
6
|
-
from hypothesis_jsonschema._canonicalise import (
|
|
7
|
-
SCHEMA_KEYS as SCHEMA_KEYS_TUPLE,
|
|
8
|
-
)
|
|
9
|
-
from hypothesis_jsonschema._canonicalise import (
|
|
10
|
-
SCHEMA_OBJECT_KEYS as SCHEMA_OBJECT_KEYS_TUPLE,
|
|
11
|
-
)
|
|
12
|
-
from hypothesis_jsonschema._canonicalise import canonicalish, merged
|
|
13
|
-
|
|
14
6
|
from schemathesis.core.jsonschema import ALL_KEYWORDS
|
|
15
7
|
from schemathesis.core.jsonschema.bundler import BUNDLE_STORAGE_KEY, bundle
|
|
16
8
|
from schemathesis.core.jsonschema.types import JsonSchema, JsonSchemaObject
|
|
@@ -23,8 +15,23 @@ if TYPE_CHECKING:
|
|
|
23
15
|
from schemathesis.core.compat import RefResolver
|
|
24
16
|
|
|
25
17
|
ROOT_POINTER = "/"
|
|
26
|
-
SCHEMA_KEYS = frozenset(
|
|
27
|
-
|
|
18
|
+
SCHEMA_KEYS = frozenset(
|
|
19
|
+
{
|
|
20
|
+
"propertyNames",
|
|
21
|
+
"contains",
|
|
22
|
+
"if",
|
|
23
|
+
"items",
|
|
24
|
+
"oneOf",
|
|
25
|
+
"anyOf",
|
|
26
|
+
"additionalProperties",
|
|
27
|
+
"then",
|
|
28
|
+
"else",
|
|
29
|
+
"not",
|
|
30
|
+
"additionalItems",
|
|
31
|
+
"allOf",
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
SCHEMA_OBJECT_KEYS = frozenset({"dependencies", "properties", "patternProperties"})
|
|
28
35
|
|
|
29
36
|
|
|
30
37
|
def resolve_all_refs(schema: JsonSchemaObject) -> dict[str, Any]:
|
|
@@ -48,6 +55,8 @@ def resolve_all_refs(schema: JsonSchemaObject) -> dict[str, Any]:
|
|
|
48
55
|
|
|
49
56
|
|
|
50
57
|
def resolve_all_refs_inner(schema: JsonSchema, *, resolve: Callable[[str], dict[str, Any]]) -> dict[str, Any]:
|
|
58
|
+
from hypothesis_jsonschema._canonicalise import merged
|
|
59
|
+
|
|
51
60
|
if schema is True:
|
|
52
61
|
return {}
|
|
53
62
|
if schema is False:
|
|
@@ -80,6 +89,8 @@ def resolve_all_refs_inner(schema: JsonSchema, *, resolve: Callable[[str], dict[
|
|
|
80
89
|
|
|
81
90
|
def canonicalize(schema: dict[str, Any], resolver: RefResolver) -> Mapping[str, Any]:
|
|
82
91
|
"""Transform the input schema into its canonical-ish form."""
|
|
92
|
+
from hypothesis_jsonschema._canonicalise import canonicalish
|
|
93
|
+
|
|
83
94
|
# Canonicalisation in `hypothesis_jsonschema` requires all references to be resovable and non-recursive
|
|
84
95
|
# On the Schemathesis side bundling solves this problem
|
|
85
96
|
bundled = bundle(schema, resolver, inline_recursive=True)
|
|
@@ -22,6 +22,7 @@ from werkzeug.routing import Map, MapAdapter, Rule
|
|
|
22
22
|
|
|
23
23
|
from schemathesis.core.adapter import ResponsesContainer
|
|
24
24
|
from schemathesis.core.transforms import encode_pointer
|
|
25
|
+
from schemathesis.specs.openapi.stateful.links import SCHEMATHESIS_LINK_EXTENSION
|
|
25
26
|
|
|
26
27
|
if TYPE_CHECKING:
|
|
27
28
|
from schemathesis.engine.observations import LocationHeaderEntry
|
|
@@ -39,7 +40,7 @@ class OperationById:
|
|
|
39
40
|
__slots__ = ("value", "method", "path")
|
|
40
41
|
|
|
41
42
|
def to_link_base(self) -> dict[str, Any]:
|
|
42
|
-
return {"operationId": self.value, "
|
|
43
|
+
return {"operationId": self.value, SCHEMATHESIS_LINK_EXTENSION: {"is_inferred": True}}
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
@dataclass(unsafe_hash=True)
|
|
@@ -53,7 +54,7 @@ class OperationByRef:
|
|
|
53
54
|
__slots__ = ("value", "method", "path")
|
|
54
55
|
|
|
55
56
|
def to_link_base(self) -> dict[str, Any]:
|
|
56
|
-
return {"operationRef": self.value, "
|
|
57
|
+
return {"operationRef": self.value, SCHEMATHESIS_LINK_EXTENSION: {"is_inferred": True}}
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
OperationReference = Union[OperationById, OperationByRef]
|
|
@@ -38,8 +38,19 @@ class OpenApiLink:
|
|
|
38
38
|
parameters: list[NormalizedParameter]
|
|
39
39
|
body: dict[str, Any] | NotSet
|
|
40
40
|
merge_body: bool
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
is_inferred: bool
|
|
42
|
+
|
|
43
|
+
__slots__ = (
|
|
44
|
+
"name",
|
|
45
|
+
"status_code",
|
|
46
|
+
"source",
|
|
47
|
+
"target",
|
|
48
|
+
"parameters",
|
|
49
|
+
"body",
|
|
50
|
+
"merge_body",
|
|
51
|
+
"is_inferred",
|
|
52
|
+
"_cached_extract",
|
|
53
|
+
)
|
|
43
54
|
|
|
44
55
|
def __init__(self, name: str, status_code: str, definition: dict[str, Any], source: APIOperation):
|
|
45
56
|
from schemathesis.specs.openapi.schemas import BaseOpenAPISchema
|
|
@@ -69,6 +80,7 @@ class OpenApiLink:
|
|
|
69
80
|
self.parameters = self._normalize_parameters(definition.get("parameters", {}), errors)
|
|
70
81
|
self.body = definition.get("requestBody", NOT_SET)
|
|
71
82
|
self.merge_body = extension.get("merge_body", True) if extension else True
|
|
83
|
+
self.is_inferred = extension.get("is_inferred", False) if extension else False
|
|
72
84
|
|
|
73
85
|
if errors:
|
|
74
86
|
raise InvalidTransition(
|
|
@@ -148,6 +160,7 @@ class OpenApiLink:
|
|
|
148
160
|
return Transition(
|
|
149
161
|
id=f"{self.source.label} -> [{self.status_code}] {self.name} -> {self.target.label}",
|
|
150
162
|
parent_id=output.case.id,
|
|
163
|
+
is_inferred=self.is_inferred,
|
|
151
164
|
parameters=self.extract_parameters(output),
|
|
152
165
|
request_body=self.extract_body(output),
|
|
153
166
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: schemathesis
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.5
|
|
4
4
|
Summary: Property-based testing framework for Open API and GraphQL based apps
|
|
5
5
|
Project-URL: Documentation, https://schemathesis.readthedocs.io/en/stable/
|
|
6
6
|
Project-URL: Changelog, https://github.com/schemathesis/schemathesis/blob/master/CHANGELOG.md
|
|
@@ -13,7 +13,7 @@ schemathesis/cli/core.py,sha256=ue7YUdVo3YvuzGL4s6i62NL6YqNDeVPBSnQ1znrvG2w,480
|
|
|
13
13
|
schemathesis/cli/commands/__init__.py,sha256=DNzKEnXu7GjGSVe0244ZErmygUBA3nGSyVY6JP3ixD0,3740
|
|
14
14
|
schemathesis/cli/commands/data.py,sha256=_ALywjIeCZjuaoDQFy-Kj8RZkEGqXd-Y95O47h8Jszs,171
|
|
15
15
|
schemathesis/cli/commands/run/__init__.py,sha256=_ApiSVh9q-TsJQ_-IiVBNnLCtTCDMTnOLwuJhOvbCp4,18925
|
|
16
|
-
schemathesis/cli/commands/run/context.py,sha256=
|
|
16
|
+
schemathesis/cli/commands/run/context.py,sha256=vej33l5yOhlJ5gLXDwat9WCW_XdhrHNc9pdIQQYddoY,9004
|
|
17
17
|
schemathesis/cli/commands/run/events.py,sha256=ew0TQOc9T2YBZynYWv95k9yfAk8-hGuZDLMxjT8EhvY,1595
|
|
18
18
|
schemathesis/cli/commands/run/executor.py,sha256=_koznTX0DoELPN_1mxr9K_Qg7-9MPXWdld1MFn3YG_Y,5329
|
|
19
19
|
schemathesis/cli/commands/run/filters.py,sha256=pzkNRcf5vLPSsMfnvt711GNzRSBK5iZIFjPA0fiH1N4,1701
|
|
@@ -23,7 +23,7 @@ schemathesis/cli/commands/run/handlers/__init__.py,sha256=TPZ3KdGi8m0fjlN0GjA31M
|
|
|
23
23
|
schemathesis/cli/commands/run/handlers/base.py,sha256=qUtDvtr3F6were_BznfnaPpMibGJMnQ5CA9aEzcIUBc,1306
|
|
24
24
|
schemathesis/cli/commands/run/handlers/cassettes.py,sha256=LzvQp--Ub5MXF7etet7fQD0Ufloh1R0j2X1o9dT8Z4k,19253
|
|
25
25
|
schemathesis/cli/commands/run/handlers/junitxml.py,sha256=qiFvM4-SlM67sep003SkLqPslzaEb4nOm3bkzw-DO-Q,2602
|
|
26
|
-
schemathesis/cli/commands/run/handlers/output.py,sha256=
|
|
26
|
+
schemathesis/cli/commands/run/handlers/output.py,sha256=pPp5-lJP3Zir1sTA7fmlhc-u1Jn17enXZNUerQMr56M,64166
|
|
27
27
|
schemathesis/cli/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
schemathesis/cli/ext/fs.py,sha256=dHQYBjQozQmuSSfXVp-2KWFK0ESOb_w-lV2SptfMfco,461
|
|
29
29
|
schemathesis/cli/ext/groups.py,sha256=kQ37t6qeArcKaY2y5VxyK3_KwAkBKCVm58IYV8gewds,2720
|
|
@@ -69,14 +69,14 @@ schemathesis/core/transport.py,sha256=LQcamAkFqJ0HuXQzepevAq2MCJW-uq5Nm-HE9yc7HM
|
|
|
69
69
|
schemathesis/core/validation.py,sha256=b0USkKzkWvdz3jOW1JXYc_TfYshfKZeP7xAUnMqcNoc,2303
|
|
70
70
|
schemathesis/core/version.py,sha256=dOBUWrY3-uA2NQXJp9z7EtZgkR6jYeLg8sMhQCL1mcI,205
|
|
71
71
|
schemathesis/core/jsonschema/__init__.py,sha256=gBZGsXIpK2EFfcp8x0b69dqzWAm2OeZHepKImkkLvoE,320
|
|
72
|
-
schemathesis/core/jsonschema/bundler.py,sha256=
|
|
72
|
+
schemathesis/core/jsonschema/bundler.py,sha256=rHaNAVgBn0XvAk3t9dHsyym1xK4FyBW7zR1GLejPD0A,8204
|
|
73
73
|
schemathesis/core/jsonschema/keywords.py,sha256=pjseXTfH9OItNs_Qq6ubkhNWQOrxTnwHmrP_jxrHeJU,631
|
|
74
74
|
schemathesis/core/jsonschema/references.py,sha256=c2Q4IKWUbwENNtkbFaqf8r3LLZu6GFE5YLnYQlg5tPg,6069
|
|
75
75
|
schemathesis/core/jsonschema/types.py,sha256=C7f9g8yKFuoxC5_0YNIh8QAyGU0-tj8pzTMfMDjjjVM,1248
|
|
76
76
|
schemathesis/core/output/__init__.py,sha256=SiHqONFskXl73AtP5dV29L14nZoKo7B-IeG52KZB32M,1446
|
|
77
77
|
schemathesis/core/output/sanitization.py,sha256=Ev3tae8dVwsYd7yVb2_1VBFYs92WFsQ4Eu1fGaymItE,2013
|
|
78
78
|
schemathesis/engine/__init__.py,sha256=QaFE-FinaTAaarteADo2RRMJ-Sz6hZB9TzD5KjMinIA,706
|
|
79
|
-
schemathesis/engine/context.py,sha256=
|
|
79
|
+
schemathesis/engine/context.py,sha256=YaBfwTUyTCZaMq7-jtAKFQj-Eh1aQdbZ0UNcC5d_epU,5792
|
|
80
80
|
schemathesis/engine/control.py,sha256=FXzP8dxL47j1Giqpy2-Bsr_MdMw9YiATSK_UfpFwDtk,1348
|
|
81
81
|
schemathesis/engine/core.py,sha256=qlPHnZVq2RrUe93fOciXd1hC3E1gVyF2BIWMPMeLIj8,6655
|
|
82
82
|
schemathesis/engine/errors.py,sha256=FlpEk44WRLzRkdK9m37z93EQuY3kbeMIQRGwU5e3Qm4,19005
|
|
@@ -99,13 +99,13 @@ schemathesis/generation/metrics.py,sha256=cZU5HdeAMcLFEDnTbNE56NuNq4P0N4ew-g1NEz
|
|
|
99
99
|
schemathesis/generation/modes.py,sha256=Q1fhjWr3zxabU5qdtLvKfpMFZJAwlW9pnxgenjeXTyU,481
|
|
100
100
|
schemathesis/generation/overrides.py,sha256=xI2djHsa42fzP32xpxgxO52INixKagf5DjDAWJYswM8,3890
|
|
101
101
|
schemathesis/generation/hypothesis/__init__.py,sha256=68BHULoXQC1WjFfw03ga5lvDGZ-c-J7H_fNEuUzFWRw,4976
|
|
102
|
-
schemathesis/generation/hypothesis/builder.py,sha256=
|
|
102
|
+
schemathesis/generation/hypothesis/builder.py,sha256=j7R_X9Z_50xjwIl_Z8DVBe6P1r8leVNvQME7mRLXM1A,38520
|
|
103
103
|
schemathesis/generation/hypothesis/examples.py,sha256=6eGaKUEC3elmKsaqfKj1sLvM8EHc-PWT4NRBq4NI0Rs,1409
|
|
104
104
|
schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
|
|
105
105
|
schemathesis/generation/hypothesis/reporting.py,sha256=uDVow6Ya8YFkqQuOqRsjbzsbyP4KKfr3jA7ZaY4FuKY,279
|
|
106
106
|
schemathesis/generation/hypothesis/strategies.py,sha256=RurE81E06d99YKG48dizy9346ayfNswYTt38zewmGgw,483
|
|
107
107
|
schemathesis/generation/stateful/__init__.py,sha256=s7jiJEnguIj44IsRyMi8afs-8yjIUuBbzW58bH5CHjs,1042
|
|
108
|
-
schemathesis/generation/stateful/state_machine.py,sha256=
|
|
108
|
+
schemathesis/generation/stateful/state_machine.py,sha256=25kkYImw5byNwuTtt97aNE3kTHAF8rZ-p3ax_bmd3JI,9135
|
|
109
109
|
schemathesis/graphql/__init__.py,sha256=_eO6MAPHGgiADVGRntnwtPxmuvk666sAh-FAU4cG9-0,326
|
|
110
110
|
schemathesis/graphql/checks.py,sha256=IADbxiZjgkBWrC5yzHDtohRABX6zKXk5w_zpWNwdzYo,3186
|
|
111
111
|
schemathesis/graphql/loaders.py,sha256=2tgG4HIvFmjHLr_KexVXnT8hSBM-dKG_fuXTZgE97So,9445
|
|
@@ -129,7 +129,7 @@ schemathesis/specs/graphql/scalars.py,sha256=6lew8mnwhrtg23leiEbG43mLGPLlRln8mCl
|
|
|
129
129
|
schemathesis/specs/graphql/schemas.py,sha256=GKJcnTAT1wUzzUr3r6wiTfiAdFLcgFQjYRRz7x4VQl0,14457
|
|
130
130
|
schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzVfHxtdLkCPKM,1422
|
|
131
131
|
schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
|
|
132
|
-
schemathesis/specs/openapi/_hypothesis.py,sha256=
|
|
132
|
+
schemathesis/specs/openapi/_hypothesis.py,sha256=O8vN-koBjzBVZfpD3pmgIt6ecU4ddAPHOxTAORd23Lo,22642
|
|
133
133
|
schemathesis/specs/openapi/checks.py,sha256=YYV6j6idyw2ubY4sLp-avs2OVEkAWeIihjT0xiV1RRA,30669
|
|
134
134
|
schemathesis/specs/openapi/converter.py,sha256=4a6-8STT5snF7B-t6IsOIGdK5rV16oNqsdvWL7VFf2M,6472
|
|
135
135
|
schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
|
|
@@ -162,15 +162,15 @@ schemathesis/specs/openapi/negative/types.py,sha256=a7buCcVxNBG6ILBM3A7oNTAX0lyD
|
|
|
162
162
|
schemathesis/specs/openapi/negative/utils.py,sha256=ozcOIuASufLqZSgnKUACjX-EOZrrkuNdXX0SDnLoGYA,168
|
|
163
163
|
schemathesis/specs/openapi/stateful/__init__.py,sha256=CQx2WJ3mKn5qmYRc90DqsG9w3Gx7DrB60S9HFz81STY,16663
|
|
164
164
|
schemathesis/specs/openapi/stateful/control.py,sha256=QaXLSbwQWtai5lxvvVtQV3BLJ8n5ePqSKB00XFxp-MA,3695
|
|
165
|
-
schemathesis/specs/openapi/stateful/inference.py,sha256=
|
|
166
|
-
schemathesis/specs/openapi/stateful/links.py,sha256=
|
|
167
|
-
schemathesis/specs/openapi/stateful/dependencies/__init__.py,sha256=
|
|
168
|
-
schemathesis/specs/openapi/stateful/dependencies/inputs.py,sha256=
|
|
169
|
-
schemathesis/specs/openapi/stateful/dependencies/models.py,sha256=
|
|
165
|
+
schemathesis/specs/openapi/stateful/inference.py,sha256=B99jSTDVi2yKxU7-raIb91xpacOrr0nZkEZY5Ej3eCY,9783
|
|
166
|
+
schemathesis/specs/openapi/stateful/links.py,sha256=SSA66mU50FFBz7e6sA37CfL-Vt0OY3gont72oFSvZYU,8163
|
|
167
|
+
schemathesis/specs/openapi/stateful/dependencies/__init__.py,sha256=0JM-FrY6Awv6gl-qDHaaK7pXbt_GKutBKPyIaph8apA,7842
|
|
168
|
+
schemathesis/specs/openapi/stateful/dependencies/inputs.py,sha256=1qVVIlzx52qsy55Pht9dYNtn2dewRSiHegfrBO1RD8c,10347
|
|
169
|
+
schemathesis/specs/openapi/stateful/dependencies/models.py,sha256=HxdVcVebjUFhSlSs_M8vDB-BnYfYwLGceIQAzytawrs,11324
|
|
170
170
|
schemathesis/specs/openapi/stateful/dependencies/naming.py,sha256=MGoyh1bfw2SoKzdbzpHxed9LHMjokPJTU_YErZaF-Ls,11396
|
|
171
171
|
schemathesis/specs/openapi/stateful/dependencies/outputs.py,sha256=zvVUfQWNIuhMkKDpz5hsVGkkvkefLt1EswpJAnHajOw,1186
|
|
172
|
-
schemathesis/specs/openapi/stateful/dependencies/resources.py,sha256=
|
|
173
|
-
schemathesis/specs/openapi/stateful/dependencies/schemas.py,sha256=
|
|
172
|
+
schemathesis/specs/openapi/stateful/dependencies/resources.py,sha256=4bgXILFgC1_y9aU_4scaNw3lkJ6laW5MMkLYh3Ph4Hg,9894
|
|
173
|
+
schemathesis/specs/openapi/stateful/dependencies/schemas.py,sha256=yMu13RsXIPDeZT1tATTxI1vkpYhjs-XFSFEvx3_Xh_Q,14094
|
|
174
174
|
schemathesis/specs/openapi/types/__init__.py,sha256=VPsWtLJle__Kodw_QqtQ3OuvBzBcCIKsTOrXy3eA7OU,66
|
|
175
175
|
schemathesis/specs/openapi/types/v3.py,sha256=Vondr9Amk6JKCIM6i6RGcmTUjFfPgOOqzBXqerccLpo,1468
|
|
176
176
|
schemathesis/transport/__init__.py,sha256=6yg_RfV_9L0cpA6qpbH-SL9_3ggtHQji9CZrpIkbA6s,5321
|
|
@@ -179,8 +179,8 @@ schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEz
|
|
|
179
179
|
schemathesis/transport/requests.py,sha256=wriRI9fprTplE_qEZLEz1TerX6GwkE3pwr6ZnU2o6vQ,10648
|
|
180
180
|
schemathesis/transport/serialization.py,sha256=GwO6OAVTmL1JyKw7HiZ256tjV4CbrRbhQN0ep1uaZwI,11157
|
|
181
181
|
schemathesis/transport/wsgi.py,sha256=kQtasFre6pjdJWRKwLA_Qb-RyQHCFNpaey9ubzlFWKI,5907
|
|
182
|
-
schemathesis-4.3.
|
|
183
|
-
schemathesis-4.3.
|
|
184
|
-
schemathesis-4.3.
|
|
185
|
-
schemathesis-4.3.
|
|
186
|
-
schemathesis-4.3.
|
|
182
|
+
schemathesis-4.3.5.dist-info/METADATA,sha256=f9Q8lbfSrfHg97P7fabHN6K0KN6qVpqFJjfg2B_w2QA,8540
|
|
183
|
+
schemathesis-4.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
184
|
+
schemathesis-4.3.5.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
|
|
185
|
+
schemathesis-4.3.5.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
|
|
186
|
+
schemathesis-4.3.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|