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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from schemathesis.pytest.lazy import LazySchema
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def from_fixture(name: str) -> LazySchema:
|
|
10
|
+
"""Create a lazy schema loader that resolves a pytest fixture at test runtime.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
name: Name of the pytest fixture that returns a schema object
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
```python
|
|
17
|
+
import pytest
|
|
18
|
+
import schemathesis
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def api_schema():
|
|
22
|
+
return schemathesis.openapi.from_url("https://api.example.com/openapi.json")
|
|
23
|
+
|
|
24
|
+
# Create lazy schema from fixture
|
|
25
|
+
schema = schemathesis.pytest.from_fixture("api_schema")
|
|
26
|
+
|
|
27
|
+
# Use with parametrize to generate tests
|
|
28
|
+
@schema.parametrize()
|
|
29
|
+
def test_api(case):
|
|
30
|
+
case.call_and_validate()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
from schemathesis.pytest.lazy import LazySchema
|
|
35
|
+
|
|
36
|
+
return LazySchema(name)
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import unittest
|
|
5
|
+
from functools import partial
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, Generator, Type, cast
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from _pytest import nodes
|
|
10
|
+
from _pytest.config import hookimpl
|
|
11
|
+
from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
|
|
12
|
+
from hypothesis.errors import FailedHealthCheck, InvalidArgument, Unsatisfiable
|
|
13
|
+
from jsonschema.exceptions import SchemaError
|
|
14
|
+
|
|
15
|
+
from schemathesis.core.control import SkipTest
|
|
16
|
+
from schemathesis.core.errors import (
|
|
17
|
+
SERIALIZERS_SUGGESTION_MESSAGE,
|
|
18
|
+
IncorrectUsage,
|
|
19
|
+
InvalidHeadersExample,
|
|
20
|
+
InvalidRegexPattern,
|
|
21
|
+
InvalidSchema,
|
|
22
|
+
SchemathesisError,
|
|
23
|
+
SerializationNotPossible,
|
|
24
|
+
format_exception,
|
|
25
|
+
)
|
|
26
|
+
from schemathesis.core.failures import FailureGroup
|
|
27
|
+
from schemathesis.core.marks import Mark
|
|
28
|
+
from schemathesis.core.result import Ok, Result
|
|
29
|
+
from schemathesis.generation import overrides
|
|
30
|
+
from schemathesis.generation.hypothesis.given import (
|
|
31
|
+
GivenArgsMark,
|
|
32
|
+
GivenKwargsMark,
|
|
33
|
+
is_given_applied,
|
|
34
|
+
merge_given_args,
|
|
35
|
+
validate_given_args,
|
|
36
|
+
)
|
|
37
|
+
from schemathesis.generation.hypothesis.reporting import (
|
|
38
|
+
HealthCheckTipStyle,
|
|
39
|
+
build_health_check_error,
|
|
40
|
+
build_unsatisfiable_error,
|
|
41
|
+
ignore_hypothesis_output,
|
|
42
|
+
)
|
|
43
|
+
from schemathesis.pytest.control_flow import fail_on_no_matches
|
|
44
|
+
from schemathesis.schemas import APIOperation
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from _pytest.fixtures import FuncFixtureInfo
|
|
48
|
+
|
|
49
|
+
from schemathesis.schemas import BaseSchema
|
|
50
|
+
|
|
51
|
+
GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE = (
|
|
52
|
+
"Unsupported test setup. Tests using `@schema.given` cannot be combined with explicit schema examples in the same "
|
|
53
|
+
"function. Separate these tests into distinct functions to avoid conflicts."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _is_schema(value: object) -> bool:
|
|
58
|
+
from schemathesis.schemas import BaseSchema
|
|
59
|
+
|
|
60
|
+
return isinstance(value, BaseSchema)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
SchemaHandleMark = Mark["BaseSchema"](attr_name="schema", check=_is_schema)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class SchemathesisFunction(Function):
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
*args: Any,
|
|
70
|
+
test_func: Callable,
|
|
71
|
+
test_name: str | None = None,
|
|
72
|
+
**kwargs: Any,
|
|
73
|
+
) -> None:
|
|
74
|
+
super().__init__(*args, **kwargs)
|
|
75
|
+
self.test_function = test_func
|
|
76
|
+
self.test_name = test_name
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class SchemathesisCase(PyCollector):
|
|
80
|
+
def __init__(self, test_function: Callable, schema: BaseSchema, *args: Any, **kwargs: Any) -> None:
|
|
81
|
+
self.given_kwargs: dict[str, Any]
|
|
82
|
+
given_args = GivenArgsMark.get(test_function)
|
|
83
|
+
given_kwargs = GivenKwargsMark.get(test_function)
|
|
84
|
+
|
|
85
|
+
assert given_args is not None
|
|
86
|
+
assert given_kwargs is not None
|
|
87
|
+
|
|
88
|
+
def _init_with_valid_test(_test_function: Callable, _args: tuple, _kwargs: dict[str, Any]) -> None:
|
|
89
|
+
self.test_function = _test_function
|
|
90
|
+
self.is_invalid_test = False
|
|
91
|
+
self.given_kwargs = merge_given_args(test_function, _args, _kwargs)
|
|
92
|
+
|
|
93
|
+
if is_given_applied(test_function):
|
|
94
|
+
failing_test = validate_given_args(test_function, given_args, given_kwargs)
|
|
95
|
+
if failing_test is not None:
|
|
96
|
+
self.test_function = failing_test
|
|
97
|
+
self.is_invalid_test = True
|
|
98
|
+
self.given_kwargs = {}
|
|
99
|
+
else:
|
|
100
|
+
_init_with_valid_test(test_function, given_args, given_kwargs)
|
|
101
|
+
else:
|
|
102
|
+
_init_with_valid_test(test_function, given_args, given_kwargs)
|
|
103
|
+
self.schema = schema
|
|
104
|
+
super().__init__(*args, **kwargs)
|
|
105
|
+
|
|
106
|
+
def _get_test_name(self, operation: APIOperation) -> str:
|
|
107
|
+
return f"{self.name}[{operation.label}]"
|
|
108
|
+
|
|
109
|
+
def _gen_items(self, result: Result[APIOperation, InvalidSchema]) -> Generator[SchemathesisFunction, None, None]:
|
|
110
|
+
"""Generate all tests for the given API operation.
|
|
111
|
+
|
|
112
|
+
Could produce more than one test item if
|
|
113
|
+
parametrization is applied via ``pytest.mark.parametrize`` or ``pytest_generate_tests``.
|
|
114
|
+
|
|
115
|
+
This implementation is based on the original one in pytest, but with slight adjustments
|
|
116
|
+
to produce tests out of hypothesis ones.
|
|
117
|
+
"""
|
|
118
|
+
from schemathesis.checks import load_all_checks
|
|
119
|
+
from schemathesis.generation.hypothesis.builder import (
|
|
120
|
+
HypothesisTestConfig,
|
|
121
|
+
HypothesisTestMode,
|
|
122
|
+
create_test,
|
|
123
|
+
make_async_test,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
load_all_checks()
|
|
127
|
+
|
|
128
|
+
is_trio_test = False
|
|
129
|
+
for mark in getattr(self.test_function, "pytestmark", []):
|
|
130
|
+
if mark.name == "trio":
|
|
131
|
+
is_trio_test = True
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
if isinstance(result, Ok):
|
|
135
|
+
operation = result.ok()
|
|
136
|
+
if self.is_invalid_test:
|
|
137
|
+
funcobj = self.test_function
|
|
138
|
+
else:
|
|
139
|
+
as_strategy_kwargs = {}
|
|
140
|
+
|
|
141
|
+
auth = self.schema.config.auth_for(operation=operation)
|
|
142
|
+
if auth is not None:
|
|
143
|
+
from requests.auth import _basic_auth_str
|
|
144
|
+
|
|
145
|
+
as_strategy_kwargs["headers"] = {"Authorization": _basic_auth_str(*auth)}
|
|
146
|
+
headers = self.schema.config.headers_for(operation=operation)
|
|
147
|
+
if headers:
|
|
148
|
+
as_strategy_kwargs["headers"] = headers
|
|
149
|
+
|
|
150
|
+
override = overrides.for_operation(operation=operation, config=self.schema.config)
|
|
151
|
+
if override is not None:
|
|
152
|
+
for location, entry in override.items():
|
|
153
|
+
if entry:
|
|
154
|
+
as_strategy_kwargs[location.container_name] = entry
|
|
155
|
+
modes = []
|
|
156
|
+
phases = self.schema.config.phases_for(operation=operation)
|
|
157
|
+
if phases.examples.enabled:
|
|
158
|
+
modes.append(HypothesisTestMode.EXAMPLES)
|
|
159
|
+
if phases.fuzzing.enabled:
|
|
160
|
+
modes.append(HypothesisTestMode.FUZZING)
|
|
161
|
+
if phases.coverage.enabled:
|
|
162
|
+
modes.append(HypothesisTestMode.COVERAGE)
|
|
163
|
+
|
|
164
|
+
funcobj = create_test(
|
|
165
|
+
operation=operation,
|
|
166
|
+
test_func=self.test_function,
|
|
167
|
+
config=HypothesisTestConfig(
|
|
168
|
+
modes=modes,
|
|
169
|
+
settings=self.schema.config.get_hypothesis_settings(operation=operation),
|
|
170
|
+
given_kwargs=self.given_kwargs,
|
|
171
|
+
project=self.schema.config,
|
|
172
|
+
as_strategy_kwargs=as_strategy_kwargs,
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
if inspect.iscoroutinefunction(self.test_function):
|
|
176
|
+
# `pytest-trio` expects a coroutine function
|
|
177
|
+
if is_trio_test:
|
|
178
|
+
funcobj.hypothesis.inner_test = self.test_function # type: ignore[attr-defined]
|
|
179
|
+
else:
|
|
180
|
+
funcobj.hypothesis.inner_test = make_async_test(self.test_function) # type: ignore[attr-defined]
|
|
181
|
+
name = self._get_test_name(operation)
|
|
182
|
+
else:
|
|
183
|
+
error = result.err()
|
|
184
|
+
funcobj = error.as_failing_test_function()
|
|
185
|
+
name = self.name
|
|
186
|
+
if error.method:
|
|
187
|
+
name += f"[{error.method.upper()} {error.path}]"
|
|
188
|
+
else:
|
|
189
|
+
name += f"[{error.path}]"
|
|
190
|
+
|
|
191
|
+
cls = self._get_class_parent()
|
|
192
|
+
definition: FunctionDefinition = FunctionDefinition.from_parent(
|
|
193
|
+
name=self.name, parent=self.parent, callobj=funcobj
|
|
194
|
+
)
|
|
195
|
+
fixturemanager = self.session._fixturemanager
|
|
196
|
+
fixtureinfo = fixturemanager.getfixtureinfo(definition, funcobj, cls)
|
|
197
|
+
|
|
198
|
+
metafunc = self._parametrize(cls, definition, fixtureinfo)
|
|
199
|
+
|
|
200
|
+
if isinstance(self.parent, Class):
|
|
201
|
+
# On pytest 7, Class collects the test methods directly, therefore
|
|
202
|
+
funcobj = partial(funcobj, self.parent.obj)
|
|
203
|
+
|
|
204
|
+
if not metafunc._calls:
|
|
205
|
+
yield SchemathesisFunction.from_parent(
|
|
206
|
+
name=name,
|
|
207
|
+
parent=self.parent,
|
|
208
|
+
callobj=funcobj,
|
|
209
|
+
fixtureinfo=fixtureinfo,
|
|
210
|
+
test_func=self.test_function,
|
|
211
|
+
originalname=self.name,
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
fixtureinfo.prune_dependency_tree()
|
|
215
|
+
for callspec in metafunc._calls:
|
|
216
|
+
subname = f"{name}[{callspec.id}]"
|
|
217
|
+
yield SchemathesisFunction.from_parent(
|
|
218
|
+
self.parent,
|
|
219
|
+
name=subname,
|
|
220
|
+
callspec=callspec,
|
|
221
|
+
callobj=funcobj,
|
|
222
|
+
fixtureinfo=fixtureinfo,
|
|
223
|
+
keywords={callspec.id: True},
|
|
224
|
+
originalname=name,
|
|
225
|
+
test_func=self.test_function,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def _get_class_parent(self) -> type | None:
|
|
229
|
+
clscol = self.getparent(Class)
|
|
230
|
+
return clscol.obj if clscol else None
|
|
231
|
+
|
|
232
|
+
def _parametrize(self, cls: type | None, definition: FunctionDefinition, fixtureinfo: FuncFixtureInfo) -> Metafunc:
|
|
233
|
+
parent = self.getparent(Module)
|
|
234
|
+
module = parent.obj if parent is not None else parent
|
|
235
|
+
# Avoiding `Metafunc` is quite problematic for now, as there are quite a lot of internals we rely on
|
|
236
|
+
metafunc = Metafunc(definition, fixtureinfo, self.config, cls=cls, module=module, _ispytest=True)
|
|
237
|
+
methods = []
|
|
238
|
+
if module is not None and hasattr(module, "pytest_generate_tests"):
|
|
239
|
+
methods.append(module.pytest_generate_tests)
|
|
240
|
+
if hasattr(cls, "pytest_generate_tests"):
|
|
241
|
+
cls = cast(Type, cls)
|
|
242
|
+
methods.append(cls().pytest_generate_tests)
|
|
243
|
+
self.ihook.pytest_generate_tests.call_extra(methods, {"metafunc": metafunc})
|
|
244
|
+
return metafunc
|
|
245
|
+
|
|
246
|
+
def collect(self) -> list[Function]: # type: ignore[return]
|
|
247
|
+
"""Generate different test items for all API operations available in the given schema."""
|
|
248
|
+
try:
|
|
249
|
+
items = [item for operation in self.schema.get_all_operations() for item in self._gen_items(operation)]
|
|
250
|
+
if not items:
|
|
251
|
+
fail_on_no_matches(self.nodeid)
|
|
252
|
+
return items
|
|
253
|
+
except Exception:
|
|
254
|
+
pytest.fail("Error during collection")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@hookimpl(hookwrapper=True) # type: ignore[misc]
|
|
258
|
+
def pytest_pycollect_makeitem(collector: nodes.Collector, name: str, obj: Any) -> Generator[None, Any, None]:
|
|
259
|
+
"""Switch to a different collector if the test is parametrized marked by schemathesis."""
|
|
260
|
+
outcome = yield
|
|
261
|
+
try:
|
|
262
|
+
schema = SchemaHandleMark.get(obj)
|
|
263
|
+
assert schema is not None
|
|
264
|
+
outcome.force_result(SchemathesisCase.from_parent(collector, test_function=obj, name=name, schema=schema))
|
|
265
|
+
except Exception:
|
|
266
|
+
outcome.get_result()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@pytest.hookimpl(tryfirst=True) # type: ignore[misc]
|
|
270
|
+
def pytest_exception_interact(node: Function, call: pytest.CallInfo, report: pytest.TestReport) -> None:
|
|
271
|
+
if call.excinfo:
|
|
272
|
+
if issubclass(call.excinfo.type, SchemathesisError) and hasattr(call.excinfo.value, "__notes__"):
|
|
273
|
+
# Hypothesis adds quite a lot of additional debug information which is not that helpful in Schemathesis
|
|
274
|
+
call.excinfo.value.__notes__.clear()
|
|
275
|
+
report.longrepr = "".join(format_exception(call.excinfo.value))
|
|
276
|
+
if call.excinfo.type is FailureGroup:
|
|
277
|
+
tb_entries = list(call.excinfo.traceback)
|
|
278
|
+
total_frames = len(tb_entries)
|
|
279
|
+
|
|
280
|
+
# Keep internal Schemathesis frames + one extra one from the caller
|
|
281
|
+
skip_frames = 0
|
|
282
|
+
for i in range(total_frames - 1, -1, -1):
|
|
283
|
+
entry = tb_entries[i]
|
|
284
|
+
|
|
285
|
+
if not str(entry.path).endswith("schemathesis/generation/case.py"):
|
|
286
|
+
skip_frames = i
|
|
287
|
+
break
|
|
288
|
+
|
|
289
|
+
report.longrepr = "".join(
|
|
290
|
+
format_exception(call.excinfo.value, with_traceback=True, skip_frames=skip_frames)
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@hookimpl(wrapper=True)
|
|
295
|
+
def pytest_pyfunc_call(pyfuncitem): # type: ignore[no-untyped-def]
|
|
296
|
+
"""It is possible to have a Hypothesis exception in runtime.
|
|
297
|
+
|
|
298
|
+
For example - kwargs validation is failed for some strategy.
|
|
299
|
+
"""
|
|
300
|
+
from schemathesis.generation.hypothesis.builder import (
|
|
301
|
+
ApiOperationMark,
|
|
302
|
+
InvalidHeadersExampleMark,
|
|
303
|
+
InvalidRegexMark,
|
|
304
|
+
MissingPathParameters,
|
|
305
|
+
NonSerializableMark,
|
|
306
|
+
UnsatisfiableExampleMark,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
__tracebackhide__ = True
|
|
310
|
+
if isinstance(pyfuncitem, SchemathesisFunction):
|
|
311
|
+
try:
|
|
312
|
+
with ignore_hypothesis_output():
|
|
313
|
+
yield
|
|
314
|
+
except InvalidArgument as exc:
|
|
315
|
+
if "Inconsistent args" in str(exc) and "@example()" in str(exc):
|
|
316
|
+
raise IncorrectUsage(GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE) from None
|
|
317
|
+
raise InvalidSchema(exc.args[0]) from None
|
|
318
|
+
except (SkipTest, unittest.SkipTest) as exc:
|
|
319
|
+
if UnsatisfiableExampleMark.is_set(pyfuncitem.obj):
|
|
320
|
+
raise Unsatisfiable("Failed to generate test cases from examples for this API operation") from None
|
|
321
|
+
non_serializable = NonSerializableMark.get(pyfuncitem.obj)
|
|
322
|
+
if non_serializable is not None:
|
|
323
|
+
media_types = ", ".join(non_serializable.media_types)
|
|
324
|
+
raise SerializationNotPossible(
|
|
325
|
+
"Failed to generate test cases from examples for this API operation because of"
|
|
326
|
+
f" unsupported payload media types: {media_types}\n{SERIALIZERS_SUGGESTION_MESSAGE}",
|
|
327
|
+
media_types=non_serializable.media_types,
|
|
328
|
+
) from None
|
|
329
|
+
invalid_regex = InvalidRegexMark.get(pyfuncitem.obj)
|
|
330
|
+
if invalid_regex is not None:
|
|
331
|
+
raise InvalidRegexPattern.from_schema_error(invalid_regex, from_examples=True) from None
|
|
332
|
+
invalid_headers = InvalidHeadersExampleMark.get(pyfuncitem.obj)
|
|
333
|
+
if invalid_headers is not None:
|
|
334
|
+
raise InvalidHeadersExample.from_headers(invalid_headers) from None
|
|
335
|
+
pytest.skip(exc.args[0])
|
|
336
|
+
except FailedHealthCheck as exc:
|
|
337
|
+
operation = ApiOperationMark.get(pyfuncitem.obj)
|
|
338
|
+
assert operation is not None
|
|
339
|
+
raise build_health_check_error(
|
|
340
|
+
operation, exc, with_tip=True, tip_style=HealthCheckTipStyle.PYTEST
|
|
341
|
+
) from None
|
|
342
|
+
except Unsatisfiable:
|
|
343
|
+
operation = ApiOperationMark.get(pyfuncitem.obj)
|
|
344
|
+
assert operation is not None
|
|
345
|
+
raise build_unsatisfiable_error(operation, with_tip=True) from None
|
|
346
|
+
except SchemaError as exc:
|
|
347
|
+
raise InvalidRegexPattern.from_schema_error(exc, from_examples=False) from exc
|
|
348
|
+
|
|
349
|
+
invalid_headers = InvalidHeadersExampleMark.get(pyfuncitem.obj)
|
|
350
|
+
if invalid_headers is not None:
|
|
351
|
+
raise InvalidHeadersExample.from_headers(invalid_headers) from None
|
|
352
|
+
|
|
353
|
+
missing_path_parameters = MissingPathParameters.get(pyfuncitem.obj)
|
|
354
|
+
if missing_path_parameters:
|
|
355
|
+
raise missing_path_parameters from None
|
|
356
|
+
else:
|
|
357
|
+
yield
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from starlette_testclient import TestClient as ASGIClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_client(app: Any) -> ASGIClient:
|
|
10
|
+
from starlette_testclient import TestClient as ASGIClient
|
|
11
|
+
|
|
12
|
+
return ASGIClient(app)
|