schemathesis 3.13.0__py3-none-any.whl → 4.4.2__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 +53 -25
- schemathesis/auths.py +507 -0
- schemathesis/checks.py +190 -25
- schemathesis/cli/__init__.py +27 -1016
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/commands/__init__.py +133 -0
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +602 -0
- schemathesis/cli/commands/run/context.py +228 -0
- schemathesis/cli/commands/run/events.py +60 -0
- schemathesis/cli/commands/run/executor.py +157 -0
- schemathesis/cli/commands/run/filters.py +53 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +45 -0
- schemathesis/cli/commands/run/handlers/cassettes.py +464 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +60 -0
- schemathesis/cli/commands/run/handlers/output.py +1750 -0
- schemathesis/cli/commands/run/loaders.py +118 -0
- schemathesis/cli/commands/run/validation.py +256 -0
- schemathesis/cli/constants.py +5 -0
- schemathesis/cli/core.py +19 -0
- schemathesis/cli/ext/fs.py +16 -0
- schemathesis/cli/ext/groups.py +203 -0
- schemathesis/cli/ext/options.py +81 -0
- schemathesis/config/__init__.py +202 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +101 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +163 -0
- schemathesis/config/_generation.py +157 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +335 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +253 -0
- schemathesis/config/_projects.py +543 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +89 -0
- schemathesis/config/schema.json +975 -0
- schemathesis/core/__init__.py +72 -0
- schemathesis/core/adapter.py +34 -0
- schemathesis/core/compat.py +32 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +100 -0
- schemathesis/core/deserialization.py +210 -0
- schemathesis/core/errors.py +588 -0
- schemathesis/core/failures.py +316 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/jsonschema/__init__.py +13 -0
- schemathesis/core/jsonschema/bundler.py +183 -0
- schemathesis/core/jsonschema/keywords.py +40 -0
- schemathesis/core/jsonschema/references.py +222 -0
- schemathesis/core/jsonschema/types.py +41 -0
- schemathesis/core/lazy_import.py +15 -0
- schemathesis/core/loaders.py +107 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/core/media_types.py +79 -0
- schemathesis/core/output/__init__.py +46 -0
- schemathesis/core/output/sanitization.py +54 -0
- schemathesis/core/parameters.py +45 -0
- schemathesis/core/rate_limit.py +60 -0
- schemathesis/core/registries.py +34 -0
- schemathesis/core/result.py +27 -0
- schemathesis/core/schema_analysis.py +17 -0
- schemathesis/core/shell.py +203 -0
- schemathesis/core/transforms.py +144 -0
- schemathesis/core/transport.py +223 -0
- schemathesis/core/validation.py +73 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +28 -0
- schemathesis/engine/context.py +152 -0
- schemathesis/engine/control.py +44 -0
- schemathesis/engine/core.py +201 -0
- schemathesis/engine/errors.py +446 -0
- schemathesis/engine/events.py +284 -0
- schemathesis/engine/observations.py +42 -0
- schemathesis/engine/phases/__init__.py +108 -0
- schemathesis/engine/phases/analysis.py +28 -0
- schemathesis/engine/phases/probes.py +172 -0
- schemathesis/engine/phases/stateful/__init__.py +68 -0
- schemathesis/engine/phases/stateful/_executor.py +364 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +220 -0
- schemathesis/engine/phases/unit/_executor.py +459 -0
- schemathesis/engine/phases/unit/_pool.py +82 -0
- schemathesis/engine/recorder.py +254 -0
- schemathesis/errors.py +47 -0
- schemathesis/filters.py +395 -0
- schemathesis/generation/__init__.py +25 -0
- schemathesis/generation/case.py +478 -0
- schemathesis/generation/coverage.py +1528 -0
- schemathesis/generation/hypothesis/__init__.py +121 -0
- schemathesis/generation/hypothesis/builder.py +992 -0
- schemathesis/generation/hypothesis/examples.py +56 -0
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +285 -0
- schemathesis/generation/meta.py +227 -0
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +20 -0
- schemathesis/generation/overrides.py +127 -0
- schemathesis/generation/stateful/__init__.py +37 -0
- schemathesis/generation/stateful/state_machine.py +294 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +285 -0
- schemathesis/hooks.py +270 -91
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +467 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +72 -0
- schemathesis/openapi/loaders.py +315 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +341 -0
- schemathesis/pytest/loaders.py +36 -0
- schemathesis/pytest/plugin.py +357 -0
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +683 -247
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/nodes.py +27 -0
- schemathesis/specs/graphql/scalars.py +86 -0
- schemathesis/specs/graphql/schemas.py +395 -123
- schemathesis/specs/graphql/validation.py +33 -0
- schemathesis/specs/openapi/__init__.py +9 -1
- schemathesis/specs/openapi/_hypothesis.py +578 -317
- schemathesis/specs/openapi/adapter/__init__.py +10 -0
- schemathesis/specs/openapi/adapter/parameters.py +729 -0
- schemathesis/specs/openapi/adapter/protocol.py +59 -0
- schemathesis/specs/openapi/adapter/references.py +19 -0
- schemathesis/specs/openapi/adapter/responses.py +368 -0
- schemathesis/specs/openapi/adapter/security.py +144 -0
- schemathesis/specs/openapi/adapter/v2.py +30 -0
- schemathesis/specs/openapi/adapter/v3_0.py +30 -0
- schemathesis/specs/openapi/adapter/v3_1.py +30 -0
- schemathesis/specs/openapi/analysis.py +96 -0
- schemathesis/specs/openapi/checks.py +753 -74
- schemathesis/specs/openapi/converter.py +176 -37
- schemathesis/specs/openapi/definitions.py +599 -4
- schemathesis/specs/openapi/examples.py +581 -165
- schemathesis/specs/openapi/expressions/__init__.py +52 -5
- schemathesis/specs/openapi/expressions/extractors.py +25 -0
- schemathesis/specs/openapi/expressions/lexer.py +34 -31
- schemathesis/specs/openapi/expressions/nodes.py +97 -46
- schemathesis/specs/openapi/expressions/parser.py +35 -13
- schemathesis/specs/openapi/formats.py +122 -0
- schemathesis/specs/openapi/media_types.py +75 -0
- schemathesis/specs/openapi/negative/__init__.py +117 -68
- schemathesis/specs/openapi/negative/mutations.py +294 -104
- schemathesis/specs/openapi/negative/utils.py +3 -6
- schemathesis/specs/openapi/patterns.py +458 -0
- schemathesis/specs/openapi/references.py +60 -81
- schemathesis/specs/openapi/schemas.py +648 -650
- schemathesis/specs/openapi/serialization.py +53 -30
- schemathesis/specs/openapi/stateful/__init__.py +404 -69
- schemathesis/specs/openapi/stateful/control.py +87 -0
- schemathesis/specs/openapi/stateful/dependencies/__init__.py +232 -0
- schemathesis/specs/openapi/stateful/dependencies/inputs.py +428 -0
- schemathesis/specs/openapi/stateful/dependencies/models.py +341 -0
- schemathesis/specs/openapi/stateful/dependencies/naming.py +491 -0
- schemathesis/specs/openapi/stateful/dependencies/outputs.py +34 -0
- schemathesis/specs/openapi/stateful/dependencies/resources.py +339 -0
- schemathesis/specs/openapi/stateful/dependencies/schemas.py +447 -0
- schemathesis/specs/openapi/stateful/inference.py +254 -0
- schemathesis/specs/openapi/stateful/links.py +219 -78
- schemathesis/specs/openapi/types/__init__.py +3 -0
- schemathesis/specs/openapi/types/common.py +23 -0
- schemathesis/specs/openapi/types/v2.py +129 -0
- schemathesis/specs/openapi/types/v3.py +134 -0
- schemathesis/specs/openapi/utils.py +7 -6
- schemathesis/specs/openapi/warnings.py +75 -0
- schemathesis/transport/__init__.py +224 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +126 -0
- schemathesis/transport/requests.py +278 -0
- schemathesis/transport/serialization.py +329 -0
- schemathesis/transport/wsgi.py +175 -0
- schemathesis-4.4.2.dist-info/METADATA +213 -0
- schemathesis-4.4.2.dist-info/RECORD +192 -0
- {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
- schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
- {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
- schemathesis/_compat.py +0 -41
- schemathesis/_hypothesis.py +0 -115
- schemathesis/cli/callbacks.py +0 -188
- schemathesis/cli/cassettes.py +0 -253
- schemathesis/cli/context.py +0 -36
- schemathesis/cli/debug.py +0 -21
- schemathesis/cli/handlers.py +0 -11
- schemathesis/cli/junitxml.py +0 -41
- schemathesis/cli/options.py +0 -51
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -508
- schemathesis/cli/output/short.py +0 -40
- schemathesis/constants.py +0 -79
- schemathesis/exceptions.py +0 -207
- schemathesis/extra/_aiohttp.py +0 -27
- schemathesis/extra/_flask.py +0 -10
- schemathesis/extra/_server.py +0 -16
- schemathesis/extra/pytest_plugin.py +0 -216
- schemathesis/failures.py +0 -131
- schemathesis/fixups/__init__.py +0 -29
- schemathesis/fixups/fast_api.py +0 -30
- schemathesis/lazy.py +0 -227
- schemathesis/models.py +0 -1041
- schemathesis/parameters.py +0 -88
- schemathesis/runner/__init__.py +0 -460
- schemathesis/runner/events.py +0 -240
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/core.py +0 -755
- schemathesis/runner/impl/solo.py +0 -85
- schemathesis/runner/impl/threadpool.py +0 -367
- schemathesis/runner/serialization.py +0 -189
- schemathesis/serializers.py +0 -233
- schemathesis/service/__init__.py +0 -3
- schemathesis/service/client.py +0 -46
- schemathesis/service/constants.py +0 -12
- schemathesis/service/events.py +0 -39
- schemathesis/service/handler.py +0 -39
- schemathesis/service/models.py +0 -7
- schemathesis/service/serialization.py +0 -153
- schemathesis/service/worker.py +0 -40
- schemathesis/specs/graphql/loaders.py +0 -215
- schemathesis/specs/openapi/constants.py +0 -7
- schemathesis/specs/openapi/expressions/context.py +0 -12
- schemathesis/specs/openapi/expressions/pointers.py +0 -29
- schemathesis/specs/openapi/filters.py +0 -44
- schemathesis/specs/openapi/links.py +0 -302
- schemathesis/specs/openapi/loaders.py +0 -453
- schemathesis/specs/openapi/parameters.py +0 -413
- schemathesis/specs/openapi/security.py +0 -129
- schemathesis/specs/openapi/validation.py +0 -24
- schemathesis/stateful.py +0 -349
- schemathesis/targets.py +0 -32
- schemathesis/types.py +0 -38
- schemathesis/utils.py +0 -436
- schemathesis-3.13.0.dist-info/METADATA +0 -202
- schemathesis-3.13.0.dist-info/RECORD +0 -91
- schemathesis-3.13.0.dist-info/entry_points.txt +0 -6
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
schemathesis/stateful.py
DELETED
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
import json
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Generator, List, Optional, Tuple
|
|
4
|
-
|
|
5
|
-
import attr
|
|
6
|
-
import hypothesis
|
|
7
|
-
from hypothesis.stateful import RuleBasedStateMachine
|
|
8
|
-
from requests.structures import CaseInsensitiveDict
|
|
9
|
-
from starlette.applications import Starlette
|
|
10
|
-
|
|
11
|
-
from .constants import DataGenerationMethod
|
|
12
|
-
from .exceptions import InvalidSchema
|
|
13
|
-
from .models import APIOperation, Case, CheckFunction
|
|
14
|
-
from .utils import NOT_SET, GenericResponse, Ok, Result
|
|
15
|
-
|
|
16
|
-
if TYPE_CHECKING:
|
|
17
|
-
from .schemas import BaseSchema
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class Stateful(enum.Enum):
|
|
21
|
-
links = 1
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@attr.s(slots=True, hash=False) # pragma: no mutate
|
|
25
|
-
class ParsedData:
|
|
26
|
-
"""A structure that holds information parsed from a test outcome.
|
|
27
|
-
|
|
28
|
-
It is used later to create a new version of an API operation that will reuse this data.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
parameters: Dict[str, Any] = attr.ib() # pragma: no mutate
|
|
32
|
-
body: Any = attr.ib(default=NOT_SET) # pragma: no mutate
|
|
33
|
-
|
|
34
|
-
def __hash__(self) -> int:
|
|
35
|
-
"""Custom hash simplifies deduplication of parsed data."""
|
|
36
|
-
value = hash(tuple(self.parameters.items())) # parameters never contain nested dicts / lists
|
|
37
|
-
if self.body is not NOT_SET:
|
|
38
|
-
if isinstance(self.body, (dict, list)):
|
|
39
|
-
# The simplest way to get a hash of a potentially nested structure
|
|
40
|
-
value ^= hash(json.dumps(self.body, sort_keys=True))
|
|
41
|
-
else:
|
|
42
|
-
# These types should be hashable
|
|
43
|
-
value ^= hash(self.body)
|
|
44
|
-
return value
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@attr.s(slots=True) # pragma: no mutate
|
|
48
|
-
class StatefulTest:
|
|
49
|
-
"""A template for a test that will be executed after another one by reusing the outcomes from it."""
|
|
50
|
-
|
|
51
|
-
name: str = attr.ib() # pragma: no mutate
|
|
52
|
-
|
|
53
|
-
def parse(self, case: Case, response: GenericResponse) -> ParsedData:
|
|
54
|
-
raise NotImplementedError
|
|
55
|
-
|
|
56
|
-
def make_operation(self, collected: List[ParsedData]) -> APIOperation:
|
|
57
|
-
raise NotImplementedError
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@attr.s(slots=True) # pragma: no mutate
|
|
61
|
-
class StatefulData:
|
|
62
|
-
"""Storage for data that will be used in later tests."""
|
|
63
|
-
|
|
64
|
-
stateful_test: StatefulTest = attr.ib() # pragma: no mutate
|
|
65
|
-
container: List[ParsedData] = attr.ib(factory=list) # pragma: no mutate
|
|
66
|
-
|
|
67
|
-
def make_operation(self) -> APIOperation:
|
|
68
|
-
return self.stateful_test.make_operation(self.container)
|
|
69
|
-
|
|
70
|
-
def store(self, case: Case, response: GenericResponse) -> None:
|
|
71
|
-
"""Parse and store data for a stateful test."""
|
|
72
|
-
parsed = self.stateful_test.parse(case, response)
|
|
73
|
-
self.container.append(parsed)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@attr.s(slots=True) # pragma: no mutate
|
|
77
|
-
class Feedback:
|
|
78
|
-
"""Handler for feedback from tests.
|
|
79
|
-
|
|
80
|
-
Provides a way to control runner's behavior from tests.
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
stateful: Optional[Stateful] = attr.ib() # pragma: no mutate
|
|
84
|
-
operation: APIOperation = attr.ib(repr=False) # pragma: no mutate
|
|
85
|
-
stateful_tests: Dict[str, StatefulData] = attr.ib(factory=dict, repr=False) # pragma: no mutate
|
|
86
|
-
|
|
87
|
-
def add_test_case(self, case: Case, response: GenericResponse) -> None:
|
|
88
|
-
"""Store test data to reuse it in the future additional tests."""
|
|
89
|
-
for stateful_test in case.operation.get_stateful_tests(response, self.stateful):
|
|
90
|
-
data = self.stateful_tests.setdefault(stateful_test.name, StatefulData(stateful_test))
|
|
91
|
-
data.store(case, response)
|
|
92
|
-
|
|
93
|
-
def get_stateful_tests(
|
|
94
|
-
self, test: Callable, settings: Optional[hypothesis.settings], seed: Optional[int]
|
|
95
|
-
) -> Generator[Tuple[Result[Tuple[APIOperation, Callable], InvalidSchema], DataGenerationMethod], None, None]:
|
|
96
|
-
"""Generate additional tests that use data from the previous ones."""
|
|
97
|
-
from ._hypothesis import create_test # pylint: disable=import-outside-toplevel
|
|
98
|
-
|
|
99
|
-
for data in self.stateful_tests.values():
|
|
100
|
-
operation = data.make_operation()
|
|
101
|
-
for data_generation_method in operation.schema.data_generation_methods:
|
|
102
|
-
test_function = create_test(
|
|
103
|
-
operation=operation,
|
|
104
|
-
test=test,
|
|
105
|
-
settings=settings,
|
|
106
|
-
seed=seed,
|
|
107
|
-
data_generation_method=data_generation_method,
|
|
108
|
-
)
|
|
109
|
-
yield Ok((operation, test_function)), data_generation_method
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
@attr.s(slots=True) # pragma: no mutate
|
|
113
|
-
class StepResult:
|
|
114
|
-
"""Output from a single transition of a state machine."""
|
|
115
|
-
|
|
116
|
-
response: GenericResponse = attr.ib() # pragma: no mutate
|
|
117
|
-
case: Case = attr.ib() # pragma: no mutate
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
class Direction:
|
|
121
|
-
name: str
|
|
122
|
-
status_code: str
|
|
123
|
-
operation: APIOperation
|
|
124
|
-
|
|
125
|
-
def set_data(self, case: Case, **kwargs: Any) -> None:
|
|
126
|
-
raise NotImplementedError
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def _print_case(case: Case) -> str:
|
|
130
|
-
operation = f"state.schema['{case.operation.path}']['{case.operation.method.upper()}']"
|
|
131
|
-
data = [
|
|
132
|
-
f"{name}={repr(getattr(case, name))}"
|
|
133
|
-
for name in ("path_parameters", "headers", "cookies", "query", "body", "media_type")
|
|
134
|
-
if getattr(case, name) not in (None, NOT_SET)
|
|
135
|
-
]
|
|
136
|
-
return f"{operation}.make_case({', '.join(data)})"
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
@attr.s(slots=True, repr=False) # pragma: no mutate
|
|
140
|
-
class _DirectionWrapper:
|
|
141
|
-
"""Purely to avoid modification of `Direction.__repr__`."""
|
|
142
|
-
|
|
143
|
-
direction: Direction = attr.ib() # pragma: no mutate
|
|
144
|
-
|
|
145
|
-
def __repr__(self) -> str:
|
|
146
|
-
path = self.direction.operation.path
|
|
147
|
-
method = self.direction.operation.method.upper()
|
|
148
|
-
return f"state.schema['{path}']['{method}'].links['{self.direction.status_code}']['{self.direction.name}']"
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
class APIStateMachine(RuleBasedStateMachine):
|
|
152
|
-
"""The base class for state machines generated from API schemas.
|
|
153
|
-
|
|
154
|
-
Exposes additional extension points in the testing process.
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
# This is a convenience attribute, which happened to clash with `RuleBasedStateMachine` instance level attribute
|
|
158
|
-
# They don't interfere, since it is properly overridden on the Hypothesis side, but it is likely that this
|
|
159
|
-
# attribute will be renamed in the future
|
|
160
|
-
bundles: ClassVar[Dict[str, CaseInsensitiveDict]] # type: ignore
|
|
161
|
-
schema: "BaseSchema"
|
|
162
|
-
|
|
163
|
-
def __init__(self) -> None:
|
|
164
|
-
super().__init__() # type: ignore
|
|
165
|
-
self.setup()
|
|
166
|
-
|
|
167
|
-
def _pretty_print(self, value: Any) -> str:
|
|
168
|
-
if isinstance(value, Case):
|
|
169
|
-
return _print_case(value)
|
|
170
|
-
if isinstance(value, tuple) and len(value) == 2:
|
|
171
|
-
result, direction = value
|
|
172
|
-
wrapper = _DirectionWrapper(direction)
|
|
173
|
-
return super()._pretty_print((result, wrapper)) # type: ignore
|
|
174
|
-
return super()._pretty_print(value) # type: ignore
|
|
175
|
-
|
|
176
|
-
def setup(self) -> None:
|
|
177
|
-
"""Hook method that runs unconditionally in the beginning of each test scenario.
|
|
178
|
-
|
|
179
|
-
Does nothing by default.
|
|
180
|
-
"""
|
|
181
|
-
|
|
182
|
-
def teardown(self) -> None:
|
|
183
|
-
pass
|
|
184
|
-
|
|
185
|
-
# To provide the return type in the rendered documentation
|
|
186
|
-
teardown.__doc__ = RuleBasedStateMachine.teardown.__doc__
|
|
187
|
-
|
|
188
|
-
def transform(self, result: StepResult, direction: Direction, case: Case) -> Case:
|
|
189
|
-
raise NotImplementedError
|
|
190
|
-
|
|
191
|
-
def _step(self, case: Case, previous: Optional[Tuple[StepResult, Direction]] = None) -> StepResult:
|
|
192
|
-
# This method is a proxy that is used under the hood during the state machine initialization.
|
|
193
|
-
# The whole point of having it is to make it possible to override `step`; otherwise, custom "step" is ignored.
|
|
194
|
-
# It happens because, at the point of initialization, the final class is not yet created.
|
|
195
|
-
return self.step(case, previous)
|
|
196
|
-
|
|
197
|
-
def step(self, case: Case, previous: Optional[Tuple[StepResult, Direction]] = None) -> StepResult:
|
|
198
|
-
"""A single state machine step.
|
|
199
|
-
|
|
200
|
-
:param Case case: Generated test case data that should be sent in an API call to the tested API operation.
|
|
201
|
-
:param previous: Optional result from the previous step and the direction in which this step should be done.
|
|
202
|
-
|
|
203
|
-
Schemathesis prepares data, makes a call and validates the received response.
|
|
204
|
-
It is the most high-level point to extend the testing process. You probably don't need it in most cases.
|
|
205
|
-
"""
|
|
206
|
-
if previous is not None:
|
|
207
|
-
result, direction = previous
|
|
208
|
-
case = self.transform(result, direction, case)
|
|
209
|
-
self.before_call(case)
|
|
210
|
-
kwargs = self.get_call_kwargs(case)
|
|
211
|
-
response = self.call(case, **kwargs)
|
|
212
|
-
self.after_call(response, case)
|
|
213
|
-
self.validate_response(response, case)
|
|
214
|
-
return self.store_result(response, case)
|
|
215
|
-
|
|
216
|
-
def before_call(self, case: Case) -> None:
|
|
217
|
-
"""Hook method for modifying the case data before making a request.
|
|
218
|
-
|
|
219
|
-
:param Case case: Generated test case data that should be sent in an API call to the tested API operation.
|
|
220
|
-
|
|
221
|
-
Use it if you want to inject static data, for example,
|
|
222
|
-
a query parameter that should always be used in API calls:
|
|
223
|
-
|
|
224
|
-
.. code-block:: python
|
|
225
|
-
|
|
226
|
-
class APIWorkflow(schema.as_state_machine()):
|
|
227
|
-
def before_call(self, case):
|
|
228
|
-
case.query = case.query or {}
|
|
229
|
-
case.query["test"] = "true"
|
|
230
|
-
|
|
231
|
-
You can also modify data only for some operations:
|
|
232
|
-
|
|
233
|
-
.. code-block:: python
|
|
234
|
-
|
|
235
|
-
class APIWorkflow(schema.as_state_machine()):
|
|
236
|
-
def before_call(self, case):
|
|
237
|
-
if case.method == "PUT" and case.path == "/items":
|
|
238
|
-
case.body["is_fake"] = True
|
|
239
|
-
"""
|
|
240
|
-
|
|
241
|
-
def after_call(self, response: GenericResponse, case: Case) -> None:
|
|
242
|
-
"""Hook method for additional actions with case or response instances.
|
|
243
|
-
|
|
244
|
-
:param response: Response from the application under test.
|
|
245
|
-
:param Case case: Generated test case data that should be sent in an API call to the tested API operation.
|
|
246
|
-
|
|
247
|
-
For example, you can log all response statuses by using this hook:
|
|
248
|
-
|
|
249
|
-
.. code-block:: python
|
|
250
|
-
|
|
251
|
-
import logging
|
|
252
|
-
|
|
253
|
-
logger = logging.getLogger(__file__)
|
|
254
|
-
logger.setLevel(logging.INFO)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
class APIWorkflow(schema.as_state_machine()):
|
|
258
|
-
def after_call(self, response, case):
|
|
259
|
-
logger.info(
|
|
260
|
-
"%s %s -> %d",
|
|
261
|
-
case.method,
|
|
262
|
-
case.path,
|
|
263
|
-
response.status_code,
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
# POST /users/ -> 201
|
|
268
|
-
# GET /users/{user_id} -> 200
|
|
269
|
-
# PATCH /users/{user_id} -> 200
|
|
270
|
-
# GET /users/{user_id} -> 200
|
|
271
|
-
# PATCH /users/{user_id} -> 500
|
|
272
|
-
"""
|
|
273
|
-
|
|
274
|
-
def call(self, case: Case, **kwargs: Any) -> GenericResponse:
|
|
275
|
-
"""Make a request to the API.
|
|
276
|
-
|
|
277
|
-
:param Case case: Generated test case data that should be sent in an API call to the tested API operation.
|
|
278
|
-
:param kwargs: Keyword arguments that will be passed to the appropriate ``case.call_*`` method.
|
|
279
|
-
:return: Response from the application under test.
|
|
280
|
-
|
|
281
|
-
Note that WSGI/ASGI applications are detected automatically in this method. Depending on the result of this
|
|
282
|
-
detection the state machine will call ``call``, ``call_wsgi`` or ``call_asgi`` methods.
|
|
283
|
-
|
|
284
|
-
Usually, you don't need to override this method unless you are building a different state machine on top of this
|
|
285
|
-
one and want to customize the transport layer itself.
|
|
286
|
-
"""
|
|
287
|
-
method = self._get_call_method(case)
|
|
288
|
-
return method(**kwargs)
|
|
289
|
-
|
|
290
|
-
def get_call_kwargs(self, case: Case) -> Dict[str, Any]:
|
|
291
|
-
"""Create custom keyword arguments that will be passed to the :meth:`Case.call` method.
|
|
292
|
-
|
|
293
|
-
Mostly they are proxied to the :func:`requests.request` call.
|
|
294
|
-
|
|
295
|
-
:param Case case: Generated test case data that should be sent in an API call to the tested API operation.
|
|
296
|
-
|
|
297
|
-
.. code-block:: python
|
|
298
|
-
|
|
299
|
-
class APIWorkflow(schema.as_state_machine()):
|
|
300
|
-
def get_call_kwargs(self, case):
|
|
301
|
-
return {"verify": False}
|
|
302
|
-
|
|
303
|
-
The above example disables the server's TLS certificate verification.
|
|
304
|
-
"""
|
|
305
|
-
return {}
|
|
306
|
-
|
|
307
|
-
def _get_call_method(self, case: Case) -> Callable:
|
|
308
|
-
if case.app is not None:
|
|
309
|
-
if isinstance(case.app, Starlette):
|
|
310
|
-
return case.call_asgi
|
|
311
|
-
return case.call_wsgi
|
|
312
|
-
return case.call
|
|
313
|
-
|
|
314
|
-
def validate_response(
|
|
315
|
-
self, response: GenericResponse, case: Case, additional_checks: Tuple[CheckFunction, ...] = ()
|
|
316
|
-
) -> None:
|
|
317
|
-
"""Validate an API response.
|
|
318
|
-
|
|
319
|
-
:param response: Response from the application under test.
|
|
320
|
-
:param Case case: Generated test case data that should be sent in an API call to the tested API operation.
|
|
321
|
-
:param additional_checks: A list of checks that will be run together with the default ones.
|
|
322
|
-
:raises CheckFailed: If any of the supplied checks failed.
|
|
323
|
-
|
|
324
|
-
If you need to change the default checks or provide custom validation rules, you can do it here.
|
|
325
|
-
|
|
326
|
-
.. code-block:: python
|
|
327
|
-
|
|
328
|
-
def my_check(response, case):
|
|
329
|
-
... # some assertions
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
class APIWorkflow(schema.as_state_machine()):
|
|
333
|
-
def validate_response(self, response, case):
|
|
334
|
-
case.validate_response(response, checks=(my_check,))
|
|
335
|
-
|
|
336
|
-
The state machine from the example above will execute only the ``my_check`` check instead of all
|
|
337
|
-
available checks.
|
|
338
|
-
|
|
339
|
-
Each check function should accept ``response`` as the first argument and ``case`` as the second one and raise
|
|
340
|
-
``AssertionError`` if the check fails.
|
|
341
|
-
|
|
342
|
-
**Note** that it is preferred to pass check functions as an argument to ``case.validate_response``.
|
|
343
|
-
In this case, all checks will be executed, and you'll receive a grouped exception that contains results from
|
|
344
|
-
all provided checks rather than only the first encountered exception.
|
|
345
|
-
"""
|
|
346
|
-
case.validate_response(response, additional_checks=additional_checks)
|
|
347
|
-
|
|
348
|
-
def store_result(self, response: GenericResponse, case: Case) -> StepResult:
|
|
349
|
-
return StepResult(response, case)
|
schemathesis/targets.py
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING, Callable, Tuple
|
|
2
|
-
|
|
3
|
-
import attr
|
|
4
|
-
|
|
5
|
-
from .utils import GenericResponse
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from .models import Case
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@attr.s(slots=True) # pragma: no mutate
|
|
12
|
-
class TargetContext:
|
|
13
|
-
"""Context for targeted testing.
|
|
14
|
-
|
|
15
|
-
:ivar Case case: Generated example that is being processed.
|
|
16
|
-
:ivar GenericResponse response: API response.
|
|
17
|
-
:ivar float response_time: API response time.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
case: "Case" = attr.ib() # pragma: no mutate
|
|
21
|
-
response: GenericResponse = attr.ib() # pragma: no mutate
|
|
22
|
-
response_time: float = attr.ib() # pragma: no mutate
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def response_time(context: TargetContext) -> float:
|
|
26
|
-
return context.response_time
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Target = Callable[[TargetContext], float]
|
|
30
|
-
DEFAULT_TARGETS = ()
|
|
31
|
-
OPTIONAL_TARGETS = (response_time,)
|
|
32
|
-
ALL_TARGETS: Tuple[Target, ...] = DEFAULT_TARGETS + OPTIONAL_TARGETS
|
schemathesis/types.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Set, Tuple, Union
|
|
3
|
-
|
|
4
|
-
from hypothesis.strategies import SearchStrategy
|
|
5
|
-
|
|
6
|
-
if TYPE_CHECKING:
|
|
7
|
-
from . import DataGenerationMethod
|
|
8
|
-
from .hooks import HookContext
|
|
9
|
-
|
|
10
|
-
PathLike = Union[Path, str] # pragma: no mutate
|
|
11
|
-
|
|
12
|
-
Query = Dict[str, Any] # pragma: no mutate
|
|
13
|
-
# Body can be of any Python type that corresponds to JSON Schema types + `bytes`
|
|
14
|
-
Body = Union[List, Dict[str, Any], str, int, float, bool, bytes] # pragma: no mutate
|
|
15
|
-
PathParameters = Dict[str, Any] # pragma: no mutate
|
|
16
|
-
Headers = Dict[str, Any] # pragma: no mutate
|
|
17
|
-
Cookies = Dict[str, Any] # pragma: no mutate
|
|
18
|
-
FormData = Dict[str, Any] # pragma: no mutate
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class NotSet:
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
RequestCert = Union[str, Tuple[str, str]]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# A filter for path / method
|
|
29
|
-
Filter = Union[str, List[str], Tuple[str], Set[str], NotSet] # pragma: no mutate
|
|
30
|
-
|
|
31
|
-
Hook = Union[
|
|
32
|
-
Callable[[SearchStrategy], SearchStrategy], Callable[[SearchStrategy, "HookContext"], SearchStrategy]
|
|
33
|
-
] # pragma: no mutate
|
|
34
|
-
|
|
35
|
-
RawAuth = Tuple[str, str] # pragma: no mutate
|
|
36
|
-
# Generic test with any arguments and no return
|
|
37
|
-
GenericTest = Callable[..., None] # pragma: no mutate
|
|
38
|
-
DataGenerationMethodInput = Union["DataGenerationMethod", Iterable["DataGenerationMethod"]]
|