schemathesis 4.3.12__py3-none-any.whl → 4.3.14__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/core/errors.py +80 -11
- schemathesis/engine/errors.py +1 -11
- schemathesis/engine/phases/unit/_executor.py +6 -2
- schemathesis/generation/hypothesis/builder.py +3 -0
- schemathesis/generation/hypothesis/reporting.py +132 -1
- schemathesis/generation/stateful/state_machine.py +3 -0
- schemathesis/pytest/plugin.py +6 -1
- schemathesis/specs/graphql/schemas.py +6 -2
- schemathesis/specs/openapi/schemas.py +18 -5
- schemathesis/specs/openapi/stateful/__init__.py +3 -5
- schemathesis/specs/openapi/stateful/dependencies/naming.py +13 -10
- schemathesis/specs/openapi/stateful/dependencies/resources.py +7 -1
- schemathesis/specs/openapi/stateful/dependencies/schemas.py +25 -9
- schemathesis/specs/openapi/stateful/links.py +6 -2
- {schemathesis-4.3.12.dist-info → schemathesis-4.3.14.dist-info}/METADATA +1 -1
- {schemathesis-4.3.12.dist-info → schemathesis-4.3.14.dist-info}/RECORD +19 -19
- {schemathesis-4.3.12.dist-info → schemathesis-4.3.14.dist-info}/WHEEL +0 -0
- {schemathesis-4.3.12.dist-info → schemathesis-4.3.14.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.3.12.dist-info → schemathesis-4.3.14.dist-info}/licenses/LICENSE +0 -0
schemathesis/core/errors.py
CHANGED
|
@@ -5,6 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
import enum
|
|
6
6
|
import re
|
|
7
7
|
import traceback
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from textwrap import indent
|
|
8
10
|
from types import TracebackType
|
|
9
11
|
from typing import TYPE_CHECKING, Any, Callable, NoReturn
|
|
10
12
|
|
|
@@ -33,6 +35,60 @@ class SchemathesisError(Exception):
|
|
|
33
35
|
"""Base exception class for all Schemathesis errors."""
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
class DefinitionKind(str, enum.Enum):
|
|
39
|
+
SCHEMA = "Schema Object"
|
|
40
|
+
SECURITY_SCHEME = "Security Scheme Object"
|
|
41
|
+
RESPONSES = "Responses Object"
|
|
42
|
+
PARAMETER = "Parameter Object"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class SchemaLocation:
|
|
47
|
+
kind: DefinitionKind
|
|
48
|
+
# Hint about where the definition is located
|
|
49
|
+
hint: str | None
|
|
50
|
+
# Open API spec version
|
|
51
|
+
version: str
|
|
52
|
+
|
|
53
|
+
__slots__ = ("kind", "hint", "version")
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def response_schema(cls, version: str) -> SchemaLocation:
|
|
57
|
+
return cls(kind=DefinitionKind.SCHEMA, hint="in response definition", version=version)
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def maybe_from_error_path(cls, path: list[str | int], version: str) -> SchemaLocation | None:
|
|
61
|
+
if len(path) == 3 and path[:2] == ["components", "securitySchemes"]:
|
|
62
|
+
return cls(kind=DefinitionKind.SECURITY_SCHEME, hint=f"definition for `{path[2]}`", version=version)
|
|
63
|
+
if len(path) == 3 and path[:2] == ["components", "schemas"]:
|
|
64
|
+
return cls(kind=DefinitionKind.SCHEMA, hint=f"definition for `{path[2]}`", version=version)
|
|
65
|
+
if len(path) == 4 and path[0] == "paths" and path[-1] == "responses":
|
|
66
|
+
return cls(kind=DefinitionKind.RESPONSES, hint=None, version=version)
|
|
67
|
+
if len(path) == 5 and path[0] == "paths" and path[3] == "parameters":
|
|
68
|
+
return cls(kind=DefinitionKind.PARAMETER, hint=f"at index {path[4]}", version=version)
|
|
69
|
+
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def message(self) -> str:
|
|
74
|
+
message = f"Invalid {self.kind.value}"
|
|
75
|
+
if self.hint is not None:
|
|
76
|
+
message += f" {self.hint}"
|
|
77
|
+
else:
|
|
78
|
+
message += " definition"
|
|
79
|
+
return message
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def specification_url(self) -> str:
|
|
83
|
+
anchor = {
|
|
84
|
+
DefinitionKind.SCHEMA: "schema-object",
|
|
85
|
+
DefinitionKind.SECURITY_SCHEME: "security-scheme-object",
|
|
86
|
+
DefinitionKind.RESPONSES: "responses-object",
|
|
87
|
+
DefinitionKind.PARAMETER: "parameter-object",
|
|
88
|
+
}[self.kind]
|
|
89
|
+
return f"https://spec.openapis.org/oas/v{self.version}#{anchor}"
|
|
90
|
+
|
|
91
|
+
|
|
36
92
|
class InvalidSchema(SchemathesisError):
|
|
37
93
|
"""Indicates errors in API schema validation or processing."""
|
|
38
94
|
|
|
@@ -56,9 +112,16 @@ class InvalidSchema(SchemathesisError):
|
|
|
56
112
|
|
|
57
113
|
@classmethod
|
|
58
114
|
def from_jsonschema_error(
|
|
59
|
-
cls,
|
|
115
|
+
cls,
|
|
116
|
+
error: ValidationError | JsonSchemaError,
|
|
117
|
+
path: str | None,
|
|
118
|
+
method: str | None,
|
|
119
|
+
config: OutputConfig,
|
|
120
|
+
location: SchemaLocation | None = None,
|
|
60
121
|
) -> InvalidSchema:
|
|
61
|
-
if
|
|
122
|
+
if location is not None:
|
|
123
|
+
message = location.message
|
|
124
|
+
elif error.absolute_path:
|
|
62
125
|
part = error.absolute_path[-1]
|
|
63
126
|
if isinstance(part, int) and len(error.absolute_path) > 1:
|
|
64
127
|
parent = error.absolute_path[-2]
|
|
@@ -70,14 +133,18 @@ class InvalidSchema(SchemathesisError):
|
|
|
70
133
|
error_path = " -> ".join(str(entry) for entry in error.path) or "[root]"
|
|
71
134
|
message += f"\n\nLocation:\n {error_path}"
|
|
72
135
|
instance = truncate_json(error.instance, config=config)
|
|
73
|
-
message += f"\n\nProblematic definition:\n{instance}"
|
|
136
|
+
message += f"\n\nProblematic definition:\n{indent(instance, ' ')}"
|
|
74
137
|
message += "\n\nError details:\n "
|
|
75
138
|
# This default message contains the instance which we already printed
|
|
76
139
|
if "is not valid under any of the given schemas" in error.message:
|
|
77
140
|
message += "The provided definition doesn't match any of the expected formats or types."
|
|
78
141
|
else:
|
|
79
142
|
message += error.message
|
|
80
|
-
message +=
|
|
143
|
+
message += "\n\n"
|
|
144
|
+
if location is not None:
|
|
145
|
+
message += f"See: {location.specification_url}"
|
|
146
|
+
else:
|
|
147
|
+
message += SCHEMA_ERROR_SUGGESTION
|
|
81
148
|
return cls(message, path=path, method=method)
|
|
82
149
|
|
|
83
150
|
@classmethod
|
|
@@ -86,12 +153,14 @@ class InvalidSchema(SchemathesisError):
|
|
|
86
153
|
) -> InvalidSchema:
|
|
87
154
|
notes = getattr(error, "__notes__", [])
|
|
88
155
|
# Some exceptions don't have the actual reference in them, hence we add it manually via notes
|
|
89
|
-
|
|
90
|
-
message = "Unresolvable
|
|
156
|
+
reference = str(notes[0])
|
|
157
|
+
message = "Unresolvable reference in the schema"
|
|
91
158
|
# Get the pointer value from "Unresolvable JSON pointer: 'components/UnknownParameter'"
|
|
92
|
-
message += f"\n\nError details:\n
|
|
93
|
-
|
|
94
|
-
|
|
159
|
+
message += f"\n\nError details:\n Reference: {reference}"
|
|
160
|
+
if not reference.startswith(("http://", "https://", "#/")):
|
|
161
|
+
message += "\n File reference could not be resolved. Check that the file exists."
|
|
162
|
+
elif reference.startswith(("#/components", "#/definitions")):
|
|
163
|
+
message += "\n Component does not exist in the schema."
|
|
95
164
|
return cls(message, path=path, method=method)
|
|
96
165
|
|
|
97
166
|
def as_failing_test_function(self) -> Callable:
|
|
@@ -154,13 +223,13 @@ class InvalidStateMachine(SchemathesisError):
|
|
|
154
223
|
for source, target_groups in by_source.items():
|
|
155
224
|
for (target, status), transitions in target_groups.items():
|
|
156
225
|
for transition in transitions:
|
|
157
|
-
result += f"\n\n {
|
|
226
|
+
result += f"\n\n {format_transition(source, status, transition.name, target)}\n"
|
|
158
227
|
for error in transition.errors:
|
|
159
228
|
result += f"\n - {error.message}"
|
|
160
229
|
return result
|
|
161
230
|
|
|
162
231
|
|
|
163
|
-
def
|
|
232
|
+
def format_transition(source: str, status: str, transition: str, target: str) -> str:
|
|
164
233
|
return f"{source} -> [{status}] {transition} -> {target}"
|
|
165
234
|
|
|
166
235
|
|
schemathesis/engine/errors.py
CHANGED
|
@@ -128,9 +128,6 @@ class EngineErrorInfo:
|
|
|
128
128
|
if self._kind == RuntimeErrorKind.HYPOTHESIS_HEALTH_CHECK_LARGE_BASE_EXAMPLE:
|
|
129
129
|
return HEALTH_CHECK_MESSAGE_LARGE_BASE_EXAMPLE
|
|
130
130
|
|
|
131
|
-
if self._kind == RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE:
|
|
132
|
-
return f"{self._error}. Possible reasons:"
|
|
133
|
-
|
|
134
131
|
if self._kind in (
|
|
135
132
|
RuntimeErrorKind.SCHEMA_INVALID_REGULAR_EXPRESSION,
|
|
136
133
|
RuntimeErrorKind.SCHEMA_GENERIC,
|
|
@@ -147,13 +144,6 @@ class EngineErrorInfo:
|
|
|
147
144
|
if isinstance(self._error, requests.RequestException):
|
|
148
145
|
return get_request_error_extras(self._error)
|
|
149
146
|
|
|
150
|
-
if self._kind == RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE:
|
|
151
|
-
return [
|
|
152
|
-
"- Contradictory schema constraints, such as a minimum value exceeding the maximum.",
|
|
153
|
-
"- Invalid schema definitions for headers or cookies, for example allowing for non-ASCII characters.",
|
|
154
|
-
"- Excessive schema complexity, which hinders parameter generation.",
|
|
155
|
-
]
|
|
156
|
-
|
|
157
147
|
return []
|
|
158
148
|
|
|
159
149
|
@cached_property
|
|
@@ -247,7 +237,7 @@ def get_runtime_error_suggestion(error_type: RuntimeErrorKind, bold: Callable[[s
|
|
|
247
237
|
|
|
248
238
|
return {
|
|
249
239
|
RuntimeErrorKind.CONNECTION_SSL: f"Bypass SSL verification with {bold('`--tls-verify=false`')}.",
|
|
250
|
-
RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE: "
|
|
240
|
+
RuntimeErrorKind.HYPOTHESIS_UNSATISFIABLE: "Review all parameters and request body schemas for conflicting constraints.",
|
|
251
241
|
RuntimeErrorKind.SCHEMA_NO_LINKS_FOUND: "Review your endpoint filters to include linked operations",
|
|
252
242
|
RuntimeErrorKind.SCHEMA_INVALID_REGULAR_EXPRESSION: "Ensure your regex is compatible with Python's syntax.\n"
|
|
253
243
|
"For guidance, visit: https://docs.python.org/3/library/re.html",
|
|
@@ -25,6 +25,7 @@ from schemathesis.core.errors import (
|
|
|
25
25
|
InvalidRegexType,
|
|
26
26
|
InvalidSchema,
|
|
27
27
|
MalformedMediaType,
|
|
28
|
+
SchemaLocation,
|
|
28
29
|
SerializationNotPossible,
|
|
29
30
|
)
|
|
30
31
|
from schemathesis.core.failures import Failure, FailureGroup
|
|
@@ -53,7 +54,7 @@ from schemathesis.generation.hypothesis.builder import (
|
|
|
53
54
|
UnresolvableReferenceMark,
|
|
54
55
|
UnsatisfiableExampleMark,
|
|
55
56
|
)
|
|
56
|
-
from schemathesis.generation.hypothesis.reporting import ignore_hypothesis_output
|
|
57
|
+
from schemathesis.generation.hypothesis.reporting import build_unsatisfiable_error, ignore_hypothesis_output
|
|
57
58
|
|
|
58
59
|
if TYPE_CHECKING:
|
|
59
60
|
from schemathesis.schemas import APIOperation
|
|
@@ -164,7 +165,7 @@ def run_test(
|
|
|
164
165
|
except hypothesis.errors.Unsatisfiable:
|
|
165
166
|
# We need more clear error message here
|
|
166
167
|
status = Status.ERROR
|
|
167
|
-
yield non_fatal_error(
|
|
168
|
+
yield non_fatal_error(build_unsatisfiable_error(operation, with_tip=False))
|
|
168
169
|
except KeyboardInterrupt:
|
|
169
170
|
yield scenario_finished(Status.INTERRUPTED)
|
|
170
171
|
yield events.Interrupted(phase=phase)
|
|
@@ -198,6 +199,9 @@ def run_test(
|
|
|
198
199
|
path=operation.path,
|
|
199
200
|
method=operation.method,
|
|
200
201
|
config=ctx.config.output,
|
|
202
|
+
location=SchemaLocation.maybe_from_error_path(
|
|
203
|
+
list(exc.absolute_path), ctx.schema.specification.version
|
|
204
|
+
),
|
|
201
205
|
)
|
|
202
206
|
)
|
|
203
207
|
except InvalidArgument as exc:
|
|
@@ -121,6 +121,8 @@ def create_test(
|
|
|
121
121
|
kwargs=config.given_kwargs,
|
|
122
122
|
)
|
|
123
123
|
|
|
124
|
+
ApiOperationMark.set(hypothesis_test, operation)
|
|
125
|
+
|
|
124
126
|
if config.seed is not None:
|
|
125
127
|
hypothesis_test = hypothesis.seed(config.seed)(hypothesis_test)
|
|
126
128
|
|
|
@@ -957,3 +959,4 @@ InvalidHeadersExampleMark = Mark[dict[str, str]](attr_name="invalid_example_head
|
|
|
957
959
|
MissingPathParameters = Mark[InvalidSchema](attr_name="missing_path_parameters")
|
|
958
960
|
InfiniteRecursiveReferenceMark = Mark[InfiniteRecursiveReference](attr_name="infinite_recursive_reference")
|
|
959
961
|
UnresolvableReferenceMark = Mark[UnresolvableReference](attr_name="unresolvable_reference")
|
|
962
|
+
ApiOperationMark = Mark[APIOperation](attr_name="api_operation")
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from contextlib import contextmanager
|
|
2
|
-
from
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Generator
|
|
3
6
|
|
|
7
|
+
from hypothesis.errors import Unsatisfiable
|
|
4
8
|
from hypothesis.reporting import with_reporter
|
|
5
9
|
|
|
10
|
+
from schemathesis.config import OutputConfig
|
|
11
|
+
from schemathesis.core.jsonschema.types import JsonSchema
|
|
12
|
+
from schemathesis.core.output import truncate_json
|
|
13
|
+
from schemathesis.core.parameters import ParameterLocation
|
|
14
|
+
from schemathesis.generation.hypothesis.examples import generate_one
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from schemathesis.schemas import APIOperation
|
|
18
|
+
|
|
6
19
|
|
|
7
20
|
def ignore(_: str) -> None:
|
|
8
21
|
pass
|
|
@@ -12,3 +25,121 @@ def ignore(_: str) -> None:
|
|
|
12
25
|
def ignore_hypothesis_output() -> Generator:
|
|
13
26
|
with with_reporter(ignore): # type: ignore
|
|
14
27
|
yield
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
UNSATISFIABILITY_CAUSE = """ - Type mismatch (e.g., enum with strings but type: integer)
|
|
31
|
+
- Contradictory constraints (e.g., minimum > maximum)
|
|
32
|
+
- Regex that's too complex to generate values for"""
|
|
33
|
+
|
|
34
|
+
GENERIC_UNSATISFIABLE_MESSAGE = f"""Cannot generate test data for this operation
|
|
35
|
+
|
|
36
|
+
Unable to identify the specific parameter. Common causes:
|
|
37
|
+
{UNSATISFIABILITY_CAUSE}"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class UnsatisfiableParameter:
|
|
42
|
+
location: ParameterLocation
|
|
43
|
+
name: str
|
|
44
|
+
schema: JsonSchema
|
|
45
|
+
|
|
46
|
+
__slots__ = ("location", "name", "schema")
|
|
47
|
+
|
|
48
|
+
def get_error_message(self, config: OutputConfig) -> str:
|
|
49
|
+
formatted_schema = truncate_json(self.schema, config=config)
|
|
50
|
+
|
|
51
|
+
if self.location == ParameterLocation.BODY:
|
|
52
|
+
# For body, name is the media type
|
|
53
|
+
location = f"request body ({self.name})"
|
|
54
|
+
else:
|
|
55
|
+
location = f"{self.location.value} parameter '{self.name}'"
|
|
56
|
+
|
|
57
|
+
return f"""Cannot generate test data for {location}
|
|
58
|
+
Schema:
|
|
59
|
+
|
|
60
|
+
{formatted_schema}
|
|
61
|
+
|
|
62
|
+
This usually means:
|
|
63
|
+
{UNSATISFIABILITY_CAUSE}"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def find_unsatisfiable_parameter(operation: APIOperation) -> UnsatisfiableParameter | None:
|
|
67
|
+
from hypothesis_jsonschema import from_schema
|
|
68
|
+
|
|
69
|
+
for location, container in (
|
|
70
|
+
(ParameterLocation.QUERY, operation.query),
|
|
71
|
+
(ParameterLocation.PATH, operation.path_parameters),
|
|
72
|
+
(ParameterLocation.HEADER, operation.headers),
|
|
73
|
+
(ParameterLocation.COOKIE, operation.cookies),
|
|
74
|
+
(ParameterLocation.BODY, operation.body),
|
|
75
|
+
):
|
|
76
|
+
for parameter in container:
|
|
77
|
+
try:
|
|
78
|
+
generate_one(from_schema(parameter.optimized_schema))
|
|
79
|
+
except Unsatisfiable:
|
|
80
|
+
if location == ParameterLocation.BODY:
|
|
81
|
+
name = parameter.media_type
|
|
82
|
+
else:
|
|
83
|
+
name = parameter.name
|
|
84
|
+
schema = unbundle_schema_refs(parameter.optimized_schema, parameter.name_to_uri)
|
|
85
|
+
return UnsatisfiableParameter(location=location, name=name, schema=schema)
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def unbundle_schema_refs(schema: JsonSchema | list[JsonSchema], name_to_uri: dict[str, str]) -> JsonSchema:
|
|
90
|
+
if isinstance(schema, dict):
|
|
91
|
+
result: dict[str, Any] = {}
|
|
92
|
+
for key, value in schema.items():
|
|
93
|
+
if key == "$ref" and isinstance(value, str) and value.startswith("#/x-bundled/"):
|
|
94
|
+
# Extract bundled name (e.g., "schema1" from "#/x-bundled/schema1")
|
|
95
|
+
bundled_name = value.split("/")[-1]
|
|
96
|
+
if bundled_name in name_to_uri:
|
|
97
|
+
original_uri = name_to_uri[bundled_name]
|
|
98
|
+
# Extract fragment after # (e.g., "#/components/schemas/ObjectType")
|
|
99
|
+
if "#" in original_uri:
|
|
100
|
+
result[key] = "#" + original_uri.split("#", 1)[1]
|
|
101
|
+
else:
|
|
102
|
+
# Fallback if no fragment
|
|
103
|
+
result[key] = value
|
|
104
|
+
else:
|
|
105
|
+
result[key] = value
|
|
106
|
+
elif key == "x-bundled" and isinstance(value, dict):
|
|
107
|
+
# Replace x-bundled with proper components/schemas structure
|
|
108
|
+
components: dict[str, dict[str, Any]] = {"schemas": {}}
|
|
109
|
+
for bundled_name, bundled_schema in value.items():
|
|
110
|
+
if bundled_name in name_to_uri:
|
|
111
|
+
original_uri = name_to_uri[bundled_name]
|
|
112
|
+
# Extract schema name (e.g., "ObjectType" from "...#/components/schemas/ObjectType")
|
|
113
|
+
if "#/components/schemas/" in original_uri:
|
|
114
|
+
schema_name = original_uri.split("#/components/schemas/")[1]
|
|
115
|
+
components["schemas"][schema_name] = unbundle_schema_refs(bundled_schema, name_to_uri)
|
|
116
|
+
else:
|
|
117
|
+
# Fallback: keep bundled name if URI doesn't match expected pattern
|
|
118
|
+
components["schemas"][bundled_name] = unbundle_schema_refs(bundled_schema, name_to_uri)
|
|
119
|
+
else:
|
|
120
|
+
components["schemas"][bundled_name] = unbundle_schema_refs(bundled_schema, name_to_uri)
|
|
121
|
+
result["components"] = components
|
|
122
|
+
elif isinstance(value, (dict, list)):
|
|
123
|
+
# Recursively process all other values
|
|
124
|
+
result[key] = unbundle_schema_refs(value, name_to_uri)
|
|
125
|
+
else:
|
|
126
|
+
result[key] = value
|
|
127
|
+
return result
|
|
128
|
+
elif isinstance(schema, list):
|
|
129
|
+
return [unbundle_schema_refs(item, name_to_uri) for item in schema] # type: ignore
|
|
130
|
+
return schema
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def build_unsatisfiable_error(operation: APIOperation, *, with_tip: bool) -> Unsatisfiable:
|
|
134
|
+
__tracebackhide__ = True
|
|
135
|
+
unsatisfiable = find_unsatisfiable_parameter(operation)
|
|
136
|
+
|
|
137
|
+
if unsatisfiable is not None:
|
|
138
|
+
message = unsatisfiable.get_error_message(operation.schema.config.output)
|
|
139
|
+
else:
|
|
140
|
+
message = GENERIC_UNSATISFIABLE_MESSAGE
|
|
141
|
+
|
|
142
|
+
if with_tip:
|
|
143
|
+
message += "\n\nTip: Review all parameters and request body schemas for conflicting constraints"
|
|
144
|
+
|
|
145
|
+
return Unsatisfiable(message)
|
|
@@ -50,6 +50,9 @@ class StepInput:
|
|
|
50
50
|
|
|
51
51
|
@property
|
|
52
52
|
def is_applied(self) -> bool:
|
|
53
|
+
# If the transition has no parameters or body, count it as applied
|
|
54
|
+
if self.transition is not None and not self.transition.parameters and self.transition.request_body is None:
|
|
55
|
+
return True
|
|
53
56
|
return bool(self.applied_parameters)
|
|
54
57
|
|
|
55
58
|
|
schemathesis/pytest/plugin.py
CHANGED
|
@@ -34,7 +34,7 @@ from schemathesis.generation.hypothesis.given import (
|
|
|
34
34
|
merge_given_args,
|
|
35
35
|
validate_given_args,
|
|
36
36
|
)
|
|
37
|
-
from schemathesis.generation.hypothesis.reporting import ignore_hypothesis_output
|
|
37
|
+
from schemathesis.generation.hypothesis.reporting import build_unsatisfiable_error, ignore_hypothesis_output
|
|
38
38
|
from schemathesis.pytest.control_flow import fail_on_no_matches
|
|
39
39
|
from schemathesis.schemas import APIOperation
|
|
40
40
|
|
|
@@ -293,6 +293,7 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
|
|
|
293
293
|
For example - kwargs validation is failed for some strategy.
|
|
294
294
|
"""
|
|
295
295
|
from schemathesis.generation.hypothesis.builder import (
|
|
296
|
+
ApiOperationMark,
|
|
296
297
|
InvalidHeadersExampleMark,
|
|
297
298
|
InvalidRegexMark,
|
|
298
299
|
MissingPathParameters,
|
|
@@ -327,6 +328,10 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
|
|
|
327
328
|
if invalid_headers is not None:
|
|
328
329
|
raise InvalidHeadersExample.from_headers(invalid_headers) from None
|
|
329
330
|
pytest.skip(exc.args[0])
|
|
331
|
+
except Unsatisfiable:
|
|
332
|
+
operation = ApiOperationMark.get(pyfuncitem.obj)
|
|
333
|
+
assert operation is not None
|
|
334
|
+
raise build_unsatisfiable_error(operation, with_tip=True) from None
|
|
330
335
|
except SchemaError as exc:
|
|
331
336
|
raise InvalidRegexPattern.from_schema_error(exc, from_examples=False) from exc
|
|
332
337
|
|
|
@@ -19,9 +19,7 @@ from typing import (
|
|
|
19
19
|
)
|
|
20
20
|
from urllib.parse import urlsplit
|
|
21
21
|
|
|
22
|
-
import graphql
|
|
23
22
|
from hypothesis import strategies as st
|
|
24
|
-
from hypothesis_graphql import strategies as gql_st
|
|
25
23
|
from requests.structures import CaseInsensitiveDict
|
|
26
24
|
|
|
27
25
|
from schemathesis import auths
|
|
@@ -52,6 +50,7 @@ from schemathesis.schemas import (
|
|
|
52
50
|
from .scalars import CUSTOM_SCALARS, get_extra_scalar_strategies
|
|
53
51
|
|
|
54
52
|
if TYPE_CHECKING:
|
|
53
|
+
import graphql
|
|
55
54
|
from hypothesis.strategies import SearchStrategy
|
|
56
55
|
|
|
57
56
|
from schemathesis.auths import AuthStorage
|
|
@@ -143,6 +142,8 @@ class GraphQLSchema(BaseSchema):
|
|
|
143
142
|
|
|
144
143
|
@property
|
|
145
144
|
def client_schema(self) -> graphql.GraphQLSchema:
|
|
145
|
+
import graphql
|
|
146
|
+
|
|
146
147
|
if not hasattr(self, "_client_schema"):
|
|
147
148
|
self._client_schema = graphql.build_client_schema(self.raw_schema)
|
|
148
149
|
return self._client_schema
|
|
@@ -336,6 +337,9 @@ def graphql_cases(
|
|
|
336
337
|
media_type: str | None = None,
|
|
337
338
|
phase: TestPhase = TestPhase.FUZZING,
|
|
338
339
|
) -> Any:
|
|
340
|
+
import graphql
|
|
341
|
+
from hypothesis_graphql import strategies as gql_st
|
|
342
|
+
|
|
339
343
|
start = time.monotonic()
|
|
340
344
|
definition = cast(GraphQLOperationDefinition, operation.definition)
|
|
341
345
|
strategy_factory = {
|
|
@@ -27,7 +27,13 @@ from requests.structures import CaseInsensitiveDict
|
|
|
27
27
|
from schemathesis.core import INJECTED_PATH_PARAMETER_KEY, NOT_SET, NotSet, Specification, deserialization, media_types
|
|
28
28
|
from schemathesis.core.adapter import OperationParameter, ResponsesContainer
|
|
29
29
|
from schemathesis.core.compat import RefResolutionError
|
|
30
|
-
from schemathesis.core.errors import
|
|
30
|
+
from schemathesis.core.errors import (
|
|
31
|
+
SCHEMA_ERROR_SUGGESTION,
|
|
32
|
+
InfiniteRecursiveReference,
|
|
33
|
+
InvalidSchema,
|
|
34
|
+
OperationNotFound,
|
|
35
|
+
SchemaLocation,
|
|
36
|
+
)
|
|
31
37
|
from schemathesis.core.failures import Failure, FailureGroup, MalformedJson
|
|
32
38
|
from schemathesis.core.result import Err, Ok, Result
|
|
33
39
|
from schemathesis.core.transport import Response
|
|
@@ -61,7 +67,6 @@ if TYPE_CHECKING:
|
|
|
61
67
|
from schemathesis.generation.stateful import APIStateMachine
|
|
62
68
|
|
|
63
69
|
HTTP_METHODS = frozenset({"get", "put", "post", "delete", "options", "head", "patch", "trace"})
|
|
64
|
-
SCHEMA_ERROR_MESSAGE = "Ensure that the definition complies with the OpenAPI specification"
|
|
65
70
|
SCHEMA_PARSING_ERRORS = (KeyError, AttributeError, RefResolutionError, InvalidSchema, InfiniteRecursiveReference)
|
|
66
71
|
|
|
67
72
|
|
|
@@ -317,9 +322,13 @@ class BaseOpenAPISchema(BaseSchema):
|
|
|
317
322
|
self.validate()
|
|
318
323
|
except jsonschema.ValidationError as exc:
|
|
319
324
|
raise InvalidSchema.from_jsonschema_error(
|
|
320
|
-
exc,
|
|
325
|
+
exc,
|
|
326
|
+
path=path,
|
|
327
|
+
method=method,
|
|
328
|
+
config=self.config.output,
|
|
329
|
+
location=SchemaLocation.maybe_from_error_path(list(exc.absolute_path), self.specification.version),
|
|
321
330
|
) from None
|
|
322
|
-
raise InvalidSchema(
|
|
331
|
+
raise InvalidSchema(SCHEMA_ERROR_SUGGESTION, path=path, method=method) from error
|
|
323
332
|
|
|
324
333
|
def validate(self) -> None:
|
|
325
334
|
with suppress(TypeError):
|
|
@@ -540,7 +549,11 @@ class BaseOpenAPISchema(BaseSchema):
|
|
|
540
549
|
definition.validator.validate(data)
|
|
541
550
|
except jsonschema.SchemaError as exc:
|
|
542
551
|
raise InvalidSchema.from_jsonschema_error(
|
|
543
|
-
exc,
|
|
552
|
+
exc,
|
|
553
|
+
path=operation.path,
|
|
554
|
+
method=operation.method,
|
|
555
|
+
config=self.config.output,
|
|
556
|
+
location=SchemaLocation.response_schema(self.specification.version),
|
|
544
557
|
) from exc
|
|
545
558
|
except jsonschema.ValidationError as exc:
|
|
546
559
|
failures.append(
|
|
@@ -167,9 +167,7 @@ def create_state_machine(schema: BaseOpenAPISchema) -> type[APIStateMachine]:
|
|
|
167
167
|
if incoming:
|
|
168
168
|
for link in incoming:
|
|
169
169
|
bundle_name = f"{link.source.label} -> {link.status_code}"
|
|
170
|
-
name = _normalize_name(
|
|
171
|
-
f"{link.source.label} -> {link.status_code} -> {link.name} -> {target.label}"
|
|
172
|
-
)
|
|
170
|
+
name = _normalize_name(link.full_name)
|
|
173
171
|
assert name not in rules, name
|
|
174
172
|
rules[name] = precondition(is_transition_allowed(bundle_name, link.source.label, target.label))(
|
|
175
173
|
transition(
|
|
@@ -237,8 +235,8 @@ def classify_root_transitions(operations: list[APIOperation], transitions: ApiTr
|
|
|
237
235
|
|
|
238
236
|
def is_likely_root_transition(operation: APIOperation) -> bool:
|
|
239
237
|
"""Check if operation is likely to succeed as a root transition."""
|
|
240
|
-
# POST operations
|
|
241
|
-
if operation.method == "post"
|
|
238
|
+
# POST operations are likely to create resources
|
|
239
|
+
if operation.method == "post":
|
|
242
240
|
return True
|
|
243
241
|
|
|
244
242
|
# GET operations without path parameters are likely to return lists
|
|
@@ -394,13 +394,13 @@ def find_matching_field(*, parameter: str, resource: str, fields: list[str]) ->
|
|
|
394
394
|
return field
|
|
395
395
|
|
|
396
396
|
# Extract parameter components
|
|
397
|
-
parameter_prefix,
|
|
397
|
+
parameter_prefix, parameter_suffix = _split_parameter_name(parameter)
|
|
398
398
|
parameter_prefix_normalized = _normalize_for_matching(parameter_prefix)
|
|
399
399
|
|
|
400
400
|
# Parameter has resource prefix, field might not
|
|
401
401
|
# Example: `channelId` - `Channel.id`
|
|
402
402
|
if parameter_prefix and parameter_prefix_normalized == resource_normalized:
|
|
403
|
-
suffix_normalized = _normalize_for_matching(
|
|
403
|
+
suffix_normalized = _normalize_for_matching(parameter_suffix)
|
|
404
404
|
|
|
405
405
|
for field in fields:
|
|
406
406
|
field_normalized = _normalize_for_matching(field)
|
|
@@ -409,8 +409,8 @@ def find_matching_field(*, parameter: str, resource: str, fields: list[str]) ->
|
|
|
409
409
|
|
|
410
410
|
# Parameter has no prefix, field might have resource prefix
|
|
411
411
|
# Example: `id` - `Channel.channelId`
|
|
412
|
-
if not parameter_prefix and
|
|
413
|
-
expected_field_normalized = resource_normalized + _normalize_for_matching(
|
|
412
|
+
if not parameter_prefix and parameter_suffix:
|
|
413
|
+
expected_field_normalized = resource_normalized + _normalize_for_matching(parameter_suffix)
|
|
414
414
|
|
|
415
415
|
for field in fields:
|
|
416
416
|
field_normalized = _normalize_for_matching(field)
|
|
@@ -433,7 +433,7 @@ def _normalize_for_matching(text: str) -> str:
|
|
|
433
433
|
return text.lower().replace("_", "").replace("-", "")
|
|
434
434
|
|
|
435
435
|
|
|
436
|
-
def _split_parameter_name(
|
|
436
|
+
def _split_parameter_name(parameter_name: str) -> tuple[str, str]:
|
|
437
437
|
"""Split parameter into (prefix, suffix) components.
|
|
438
438
|
|
|
439
439
|
Examples:
|
|
@@ -444,13 +444,16 @@ def _split_parameter_name(param_name: str) -> tuple[str, str]:
|
|
|
444
444
|
"channel_id" -> ("channel", "_id")
|
|
445
445
|
|
|
446
446
|
"""
|
|
447
|
-
if
|
|
448
|
-
return (
|
|
447
|
+
if parameter_name.endswith("Id") and len(parameter_name) > 2:
|
|
448
|
+
return (parameter_name[:-2], "Id")
|
|
449
449
|
|
|
450
|
-
if
|
|
451
|
-
return (
|
|
450
|
+
if parameter_name.endswith("_id") and len(parameter_name) > 3:
|
|
451
|
+
return (parameter_name[:-3], "_id")
|
|
452
452
|
|
|
453
|
-
|
|
453
|
+
if parameter_name.endswith("_guid") and len(parameter_name) > 5:
|
|
454
|
+
return (parameter_name[:-5], "_guid")
|
|
455
|
+
|
|
456
|
+
return ("", parameter_name)
|
|
454
457
|
|
|
455
458
|
|
|
456
459
|
def strip_affixes(name: str, prefixes: list[str], suffixes: list[str]) -> str:
|
|
@@ -294,7 +294,13 @@ def _extract_resource_from_schema(
|
|
|
294
294
|
properties = resolved.get("properties")
|
|
295
295
|
if properties:
|
|
296
296
|
fields = sorted(properties)
|
|
297
|
-
types = {
|
|
297
|
+
types = {}
|
|
298
|
+
for field, subschema in properties.items():
|
|
299
|
+
if isinstance(subschema, dict):
|
|
300
|
+
_, resolved_subschema = maybe_resolve(subschema, resolver, "")
|
|
301
|
+
else:
|
|
302
|
+
resolved_subschema = subschema
|
|
303
|
+
types[field] = set(get_type(cast(dict, resolved_subschema)))
|
|
298
304
|
source = DefinitionSource.SCHEMA_WITH_PROPERTIES
|
|
299
305
|
else:
|
|
300
306
|
fields = []
|
|
@@ -232,30 +232,38 @@ def unwrap_schema(
|
|
|
232
232
|
_, resolved = maybe_resolve(array_schema, resolver, "")
|
|
233
233
|
pointer = f"/{encode_pointer(array_field)}"
|
|
234
234
|
|
|
235
|
+
uses_parent_ref = False
|
|
235
236
|
# Try to unwrap one more time
|
|
236
237
|
if resolved.get("type") == "array" or "items" in resolved:
|
|
237
238
|
nested_items = resolved.get("items")
|
|
238
239
|
if isinstance(nested_items, dict):
|
|
239
240
|
_, resolved_items = maybe_resolve(nested_items, resolver, "")
|
|
240
|
-
external_tag = _detect_externally_tagged_pattern(resolved_items, path)
|
|
241
|
+
external_tag = _detect_externally_tagged_pattern(resolved_items, path, parent_ref)
|
|
241
242
|
if external_tag:
|
|
242
|
-
|
|
243
|
+
external_tag_, uses_parent_ref = external_tag
|
|
244
|
+
nested_properties = resolved_items["properties"][external_tag_]
|
|
243
245
|
_, resolved = maybe_resolve(nested_properties, resolver, "")
|
|
244
|
-
pointer += f"/{encode_pointer(
|
|
246
|
+
pointer += f"/{encode_pointer(external_tag_)}"
|
|
245
247
|
|
|
248
|
+
ref = parent_ref if uses_parent_ref else array_schema.get("$ref")
|
|
246
249
|
return UnwrappedSchema(pointer=pointer, schema=resolved, ref=array_schema.get("$ref"))
|
|
247
250
|
|
|
248
251
|
# External tag
|
|
249
|
-
external_tag = _detect_externally_tagged_pattern(schema, path)
|
|
252
|
+
external_tag = _detect_externally_tagged_pattern(schema, path, parent_ref)
|
|
250
253
|
if external_tag:
|
|
251
|
-
|
|
254
|
+
external_tag_, uses_parent_ref = external_tag
|
|
255
|
+
tagged_schema = properties[external_tag_]
|
|
252
256
|
_, resolved_tagged = maybe_resolve(tagged_schema, resolver, "")
|
|
253
257
|
|
|
254
258
|
resolved = try_unwrap_all_of(resolved_tagged)
|
|
255
|
-
ref =
|
|
259
|
+
ref = (
|
|
260
|
+
parent_ref
|
|
261
|
+
if uses_parent_ref
|
|
262
|
+
else resolved.get("$ref") or resolved_tagged.get("$ref") or tagged_schema.get("$ref")
|
|
263
|
+
)
|
|
256
264
|
|
|
257
265
|
_, resolved = maybe_resolve(resolved, resolver, "")
|
|
258
|
-
return UnwrappedSchema(pointer=f"/{encode_pointer(
|
|
266
|
+
return UnwrappedSchema(pointer=f"/{encode_pointer(external_tag_)}", schema=resolved, ref=ref)
|
|
259
267
|
|
|
260
268
|
# No wrapper - single object at root
|
|
261
269
|
return UnwrappedSchema(pointer="/", schema=schema, ref=schema.get("$ref"))
|
|
@@ -391,7 +399,9 @@ def _is_pagination_wrapper(
|
|
|
391
399
|
return None
|
|
392
400
|
|
|
393
401
|
|
|
394
|
-
def _detect_externally_tagged_pattern(
|
|
402
|
+
def _detect_externally_tagged_pattern(
|
|
403
|
+
schema: Mapping[str, Any], path: str, parent_ref: str | None
|
|
404
|
+
) -> tuple[str, bool] | None:
|
|
395
405
|
"""Detect externally tagged resource pattern.
|
|
396
406
|
|
|
397
407
|
Pattern: {ResourceName: [...]} or {resourceName: [...]}
|
|
@@ -420,12 +430,18 @@ def _detect_externally_tagged_pattern(schema: Mapping[str, Any], path: str) -> s
|
|
|
420
430
|
# `data_request`
|
|
421
431
|
naming.to_snake_case(resource_name),
|
|
422
432
|
}
|
|
433
|
+
parent_names = set()
|
|
434
|
+
if parent_ref is not None:
|
|
435
|
+
maybe_resource_name = resource_name_from_ref(parent_ref)
|
|
436
|
+
parent_names.add(naming.to_plural(maybe_resource_name.lower()))
|
|
437
|
+
parent_names.add(naming.to_snake_case(maybe_resource_name))
|
|
438
|
+
possible_names = possible_names.union(parent_names)
|
|
423
439
|
|
|
424
440
|
for name, subschema in properties.items():
|
|
425
441
|
if name.lower() not in possible_names:
|
|
426
442
|
continue
|
|
427
443
|
|
|
428
444
|
if isinstance(subschema, dict) and "object" in get_type(subschema):
|
|
429
|
-
return name
|
|
445
|
+
return name, name.lower() in parent_names
|
|
430
446
|
|
|
431
447
|
return None
|
|
@@ -5,7 +5,7 @@ from functools import lru_cache
|
|
|
5
5
|
from typing import Any, Callable
|
|
6
6
|
|
|
7
7
|
from schemathesis.core import NOT_SET, NotSet
|
|
8
|
-
from schemathesis.core.errors import InvalidTransition, OperationNotFound, TransitionValidationError
|
|
8
|
+
from schemathesis.core.errors import InvalidTransition, OperationNotFound, TransitionValidationError, format_transition
|
|
9
9
|
from schemathesis.core.parameters import ParameterLocation
|
|
10
10
|
from schemathesis.core.result import Err, Ok, Result
|
|
11
11
|
from schemathesis.generation.stateful.state_machine import ExtractedParam, StepOutput, Transition
|
|
@@ -94,6 +94,10 @@ class OpenApiLink:
|
|
|
94
94
|
|
|
95
95
|
self._cached_extract = lru_cache(8)(self._extract_impl)
|
|
96
96
|
|
|
97
|
+
@property
|
|
98
|
+
def full_name(self) -> str:
|
|
99
|
+
return format_transition(self.source.label, self.status_code, self.name, self.target.label)
|
|
100
|
+
|
|
97
101
|
def _normalize_parameters(
|
|
98
102
|
self, parameters: dict[str, str], errors: list[TransitionValidationError]
|
|
99
103
|
) -> list[NormalizedParameter]:
|
|
@@ -165,7 +169,7 @@ class OpenApiLink:
|
|
|
165
169
|
def _extract_impl(self, wrapper: StepOutputWrapper) -> Transition:
|
|
166
170
|
output = wrapper.output
|
|
167
171
|
return Transition(
|
|
168
|
-
id=
|
|
172
|
+
id=self.full_name,
|
|
169
173
|
parent_id=output.case.id,
|
|
170
174
|
is_inferred=self.is_inferred,
|
|
171
175
|
parameters=self.extract_parameters(output),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: schemathesis
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.14
|
|
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
|
|
@@ -52,7 +52,7 @@ schemathesis/core/compat.py,sha256=9BWCrFoqN2sJIaiht_anxe8kLjYMR7t0iiOkXqLRUZ8,1
|
|
|
52
52
|
schemathesis/core/control.py,sha256=IzwIc8HIAEMtZWW0Q0iXI7T1niBpjvcLlbuwOSmy5O8,130
|
|
53
53
|
schemathesis/core/curl.py,sha256=jrPL9KpNHteyJ6A1oxJRSkL5bfuBeuPs3xh9Z_ml2cE,1892
|
|
54
54
|
schemathesis/core/deserialization.py,sha256=qjXUPaz_mc1OSgXzTUSkC8tuVR8wgVQtb9g3CcAF6D0,2951
|
|
55
|
-
schemathesis/core/errors.py,sha256=
|
|
55
|
+
schemathesis/core/errors.py,sha256=sr23WgbD-52n5fmC-QBn2suzNUbsB1okvXIs_L5EyR0,19918
|
|
56
56
|
schemathesis/core/failures.py,sha256=yFpAxWdEnm0Ri8z8RqRI9H7vcLH5ztOeSIi4m4SGx5g,8996
|
|
57
57
|
schemathesis/core/fs.py,sha256=ItQT0_cVwjDdJX9IiI7EnU75NI2H3_DCEyyUjzg_BgI,472
|
|
58
58
|
schemathesis/core/hooks.py,sha256=qhbkkRSf8URJ4LKv2wmKRINKpquUOgxQzWBHKWRWo3Q,475
|
|
@@ -79,7 +79,7 @@ schemathesis/engine/__init__.py,sha256=QaFE-FinaTAaarteADo2RRMJ-Sz6hZB9TzD5KjMin
|
|
|
79
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
|
-
schemathesis/engine/errors.py,sha256=
|
|
82
|
+
schemathesis/engine/errors.py,sha256=Vg64E97RS--IH6bXPPkQTDl2vw1DbasveCfL037JALU,18478
|
|
83
83
|
schemathesis/engine/events.py,sha256=jpCtMkWWfNe2jUeZh_Ly_wfZEF44EOodL-I_W4C9rgg,6594
|
|
84
84
|
schemathesis/engine/observations.py,sha256=T-5R8GeVIqvxpCMxc6vZ04UUxUTx3w7689r3Dc6bIcE,1416
|
|
85
85
|
schemathesis/engine/recorder.py,sha256=KWyWkGkZxIwSDU92jNWCJXU4G4E5WqfhLM6G1Yi7Jyo,8636
|
|
@@ -89,7 +89,7 @@ schemathesis/engine/phases/stateful/__init__.py,sha256=Lz1rgNqCfUSIz173XqCGsiMuU
|
|
|
89
89
|
schemathesis/engine/phases/stateful/_executor.py,sha256=yRpUJqKLTKMVRy7hEXPwmI23CtgGIprz341lCJwvTrU,15613
|
|
90
90
|
schemathesis/engine/phases/stateful/context.py,sha256=A7X1SLDOWFpCvFN9IiIeNVZM0emjqatmJL_k9UsO7vM,2946
|
|
91
91
|
schemathesis/engine/phases/unit/__init__.py,sha256=9dDcxyj887pktnE9YDIPNaR-vc7iqKQWIrFr77SbUTQ,8786
|
|
92
|
-
schemathesis/engine/phases/unit/_executor.py,sha256=
|
|
92
|
+
schemathesis/engine/phases/unit/_executor.py,sha256=8ryOJuAXulDum3NDR2w-ey1x7wSrEmdR9dnpL8vOcsY,17419
|
|
93
93
|
schemathesis/engine/phases/unit/_pool.py,sha256=iU0hdHDmohPnEv7_S1emcabuzbTf-Cznqwn0pGQ5wNQ,2480
|
|
94
94
|
schemathesis/generation/__init__.py,sha256=tvNO2FLiY8z3fZ_kL_QJhSgzXfnT4UqwSXMHCwfLI0g,645
|
|
95
95
|
schemathesis/generation/case.py,sha256=SLMw6zkzmeiZdaIij8_0tjTF70BrMlRSWREaqWii0uM,12508
|
|
@@ -99,12 +99,12 @@ 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=N9UiJfwUplmrCLsROHQtirm1UPt_TDqUiLv4as2qAjU,38562
|
|
103
103
|
schemathesis/generation/hypothesis/examples.py,sha256=6eGaKUEC3elmKsaqfKj1sLvM8EHc-PWT4NRBq4NI0Rs,1409
|
|
104
104
|
schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
|
|
105
|
-
schemathesis/generation/hypothesis/reporting.py,sha256=
|
|
105
|
+
schemathesis/generation/hypothesis/reporting.py,sha256=8MUT_HC_iUJ8aVfxEVQ7aPTLx2ZwqOMnTucxfP5qblM,5863
|
|
106
106
|
schemathesis/generation/stateful/__init__.py,sha256=s7jiJEnguIj44IsRyMi8afs-8yjIUuBbzW58bH5CHjs,1042
|
|
107
|
-
schemathesis/generation/stateful/state_machine.py,sha256=
|
|
107
|
+
schemathesis/generation/stateful/state_machine.py,sha256=CiVtpBEeotpNOUkYO3vJLKRe89gdT1kjguZ88vbfqs0,9500
|
|
108
108
|
schemathesis/graphql/__init__.py,sha256=_eO6MAPHGgiADVGRntnwtPxmuvk666sAh-FAU4cG9-0,326
|
|
109
109
|
schemathesis/graphql/checks.py,sha256=IADbxiZjgkBWrC5yzHDtohRABX6zKXk5w_zpWNwdzYo,3186
|
|
110
110
|
schemathesis/graphql/loaders.py,sha256=2tgG4HIvFmjHLr_KexVXnT8hSBM-dKG_fuXTZgE97So,9445
|
|
@@ -117,7 +117,7 @@ schemathesis/pytest/__init__.py,sha256=7W0q-Thcw03IAQfXE_Mo8JPZpUdHJzfu85fjK1Zdf
|
|
|
117
117
|
schemathesis/pytest/control_flow.py,sha256=F8rAPsPeNv_sJiJgbZYtTpwKWjauZmqFUaKroY2GmQI,217
|
|
118
118
|
schemathesis/pytest/lazy.py,sha256=wP0sqcVFcD-OjDIFUpYdJdFQ-BY18CVyL0iB6eHiWRw,11088
|
|
119
119
|
schemathesis/pytest/loaders.py,sha256=Sbv8e5F77_x4amLP50iwubfm6kpOhx7LhLFGsVXW5Ys,925
|
|
120
|
-
schemathesis/pytest/plugin.py,sha256=
|
|
120
|
+
schemathesis/pytest/plugin.py,sha256=j93P7Bp6V2kjkw4Xg9ZoNgd9txcDiQFHByP6rMkI2R0,14518
|
|
121
121
|
schemathesis/python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
122
122
|
schemathesis/python/asgi.py,sha256=5PyvuTBaivvyPUEi3pwJni91K1kX5Zc0u9c6c1D8a1Q,287
|
|
123
123
|
schemathesis/python/wsgi.py,sha256=uShAgo_NChbfYaV1117e6UHp0MTg7jaR0Sy_to3Jmf8,219
|
|
@@ -125,7 +125,7 @@ schemathesis/specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
|
125
125
|
schemathesis/specs/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
126
126
|
schemathesis/specs/graphql/nodes.py,sha256=bE3G1kNmqJ8OV4igBvIK-UORrkQA6Nofduf87O3TD9I,541
|
|
127
127
|
schemathesis/specs/graphql/scalars.py,sha256=6lew8mnwhrtg23leiEbG43mLGPLlRln8mClCY94XpDA,2680
|
|
128
|
-
schemathesis/specs/graphql/schemas.py,sha256=
|
|
128
|
+
schemathesis/specs/graphql/schemas.py,sha256=oNbluhfxpbxRWVIMoLn3_g-bUaVLCea29jOZiHN1MT0,14509
|
|
129
129
|
schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzVfHxtdLkCPKM,1422
|
|
130
130
|
schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
|
|
131
131
|
schemathesis/specs/openapi/_hypothesis.py,sha256=O8vN-koBjzBVZfpD3pmgIt6ecU4ddAPHOxTAORd23Lo,22642
|
|
@@ -137,7 +137,7 @@ schemathesis/specs/openapi/formats.py,sha256=4tYRdckauHxkJCmOhmdwDq_eOpHPaKloi89
|
|
|
137
137
|
schemathesis/specs/openapi/media_types.py,sha256=F5M6TKl0s6Z5X8mZpPsWDEdPBvxclKRcUOc41eEwKbo,2472
|
|
138
138
|
schemathesis/specs/openapi/patterns.py,sha256=GqPZEXMRdWENQxanWjBOalIZ2MQUjuxk21kmdiI703E,18027
|
|
139
139
|
schemathesis/specs/openapi/references.py,sha256=AW1laU23BkiRf0EEFM538vyVFLXycGUiucGVV461le0,1927
|
|
140
|
-
schemathesis/specs/openapi/schemas.py,sha256=
|
|
140
|
+
schemathesis/specs/openapi/schemas.py,sha256=ONFB8kMBrryZL_tKHWvxnBjyUHoHh_MAUqxjuVDc78c,34034
|
|
141
141
|
schemathesis/specs/openapi/serialization.py,sha256=RPNdadne5wdhsGmjSvgKLRF58wpzpRx3wura8PsHM3o,12152
|
|
142
142
|
schemathesis/specs/openapi/utils.py,sha256=XkOJT8qD-6uhq-Tmwxk_xYku1Gy5F9pKL3ldNg_DRZw,522
|
|
143
143
|
schemathesis/specs/openapi/adapter/__init__.py,sha256=YEovBgLjnXd3WGPMJXq0KbSGHezkRlEv4dNRO7_evfk,249
|
|
@@ -159,17 +159,17 @@ schemathesis/specs/openapi/negative/__init__.py,sha256=B78vps314fJOMZwlPdv7vUHo7
|
|
|
159
159
|
schemathesis/specs/openapi/negative/mutations.py,sha256=9U352xJsdZBR-Zfy1V7_X3a5i91LIUS9Zqotrzp3BLA,21000
|
|
160
160
|
schemathesis/specs/openapi/negative/types.py,sha256=a7buCcVxNBG6ILBM3A7oNTAX0lyDseEtZndBuej8MbI,174
|
|
161
161
|
schemathesis/specs/openapi/negative/utils.py,sha256=ozcOIuASufLqZSgnKUACjX-EOZrrkuNdXX0SDnLoGYA,168
|
|
162
|
-
schemathesis/specs/openapi/stateful/__init__.py,sha256=
|
|
162
|
+
schemathesis/specs/openapi/stateful/__init__.py,sha256=RpGqyjKShp2X94obaHnCR9TO6Qt_ZAarlB-awlyMzUY,18654
|
|
163
163
|
schemathesis/specs/openapi/stateful/control.py,sha256=QaXLSbwQWtai5lxvvVtQV3BLJ8n5ePqSKB00XFxp-MA,3695
|
|
164
164
|
schemathesis/specs/openapi/stateful/inference.py,sha256=B99jSTDVi2yKxU7-raIb91xpacOrr0nZkEZY5Ej3eCY,9783
|
|
165
|
-
schemathesis/specs/openapi/stateful/links.py,sha256=
|
|
165
|
+
schemathesis/specs/openapi/stateful/links.py,sha256=TEZ7wudPdRdP-pg5XNIgYbual_9n2arBKnS2n8SxiaU,8629
|
|
166
166
|
schemathesis/specs/openapi/stateful/dependencies/__init__.py,sha256=9FWF7tiP7GaOwapRFIYjsu16LxkosKCzBvzjkSTCsjU,8183
|
|
167
167
|
schemathesis/specs/openapi/stateful/dependencies/inputs.py,sha256=sQydINThS6vp9-OnTKCb_unoVP4m3Ho-0xTG0K7ps8Q,15915
|
|
168
168
|
schemathesis/specs/openapi/stateful/dependencies/models.py,sha256=Kl482Hwq2M8lYAdqGmf_8Yje3voSj1WLDUIujRUDWDQ,12286
|
|
169
|
-
schemathesis/specs/openapi/stateful/dependencies/naming.py,sha256=
|
|
169
|
+
schemathesis/specs/openapi/stateful/dependencies/naming.py,sha256=GoO_Tw04u_7ix6qsAzMDoJooXZqIRAIV8sLphL4mGnw,13084
|
|
170
170
|
schemathesis/specs/openapi/stateful/dependencies/outputs.py,sha256=zvVUfQWNIuhMkKDpz5hsVGkkvkefLt1EswpJAnHajOw,1186
|
|
171
|
-
schemathesis/specs/openapi/stateful/dependencies/resources.py,sha256=
|
|
172
|
-
schemathesis/specs/openapi/stateful/dependencies/schemas.py,sha256=
|
|
171
|
+
schemathesis/specs/openapi/stateful/dependencies/resources.py,sha256=eUWE26ZixPtSuHl7laF2asS_-6qklQRhyQlk7e99hlc,12087
|
|
172
|
+
schemathesis/specs/openapi/stateful/dependencies/schemas.py,sha256=TKM6hEuyLm5NB-jQeEbIXF7GZF-7mqYLq3OsTBRDpws,14878
|
|
173
173
|
schemathesis/specs/openapi/types/__init__.py,sha256=VPsWtLJle__Kodw_QqtQ3OuvBzBcCIKsTOrXy3eA7OU,66
|
|
174
174
|
schemathesis/specs/openapi/types/v3.py,sha256=Vondr9Amk6JKCIM6i6RGcmTUjFfPgOOqzBXqerccLpo,1468
|
|
175
175
|
schemathesis/transport/__init__.py,sha256=6yg_RfV_9L0cpA6qpbH-SL9_3ggtHQji9CZrpIkbA6s,5321
|
|
@@ -178,8 +178,8 @@ schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEz
|
|
|
178
178
|
schemathesis/transport/requests.py,sha256=wriRI9fprTplE_qEZLEz1TerX6GwkE3pwr6ZnU2o6vQ,10648
|
|
179
179
|
schemathesis/transport/serialization.py,sha256=GwO6OAVTmL1JyKw7HiZ256tjV4CbrRbhQN0ep1uaZwI,11157
|
|
180
180
|
schemathesis/transport/wsgi.py,sha256=kQtasFre6pjdJWRKwLA_Qb-RyQHCFNpaey9ubzlFWKI,5907
|
|
181
|
-
schemathesis-4.3.
|
|
182
|
-
schemathesis-4.3.
|
|
183
|
-
schemathesis-4.3.
|
|
184
|
-
schemathesis-4.3.
|
|
185
|
-
schemathesis-4.3.
|
|
181
|
+
schemathesis-4.3.14.dist-info/METADATA,sha256=TvgjAcKH_S1rOrvKQsipJY2hJbJE1s_yzxNxKATSEfM,8566
|
|
182
|
+
schemathesis-4.3.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
183
|
+
schemathesis-4.3.14.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
|
|
184
|
+
schemathesis-4.3.14.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
|
|
185
|
+
schemathesis-4.3.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|