schemathesis 3.39.16__py3-none-any.whl → 4.0.0__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 +41 -79
- schemathesis/auths.py +111 -122
- schemathesis/checks.py +169 -60
- schemathesis/cli/__init__.py +15 -2117
- schemathesis/cli/commands/__init__.py +85 -0
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +590 -0
- schemathesis/cli/commands/run/context.py +204 -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 +18 -0
- schemathesis/cli/commands/run/handlers/cassettes.py +474 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +55 -0
- schemathesis/cli/commands/run/handlers/output.py +1628 -0
- schemathesis/cli/commands/run/loaders.py +114 -0
- schemathesis/cli/commands/run/validation.py +246 -0
- schemathesis/cli/constants.py +5 -58
- schemathesis/cli/core.py +19 -0
- schemathesis/cli/ext/fs.py +16 -0
- schemathesis/cli/ext/groups.py +84 -0
- schemathesis/cli/{options.py → ext/options.py} +36 -34
- schemathesis/config/__init__.py +189 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +99 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +156 -0
- schemathesis/config/_generation.py +149 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +327 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +187 -0
- schemathesis/config/_projects.py +527 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +25 -0
- schemathesis/config/schema.json +885 -0
- schemathesis/core/__init__.py +67 -0
- schemathesis/core/compat.py +32 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +58 -0
- schemathesis/core/deserialization.py +65 -0
- schemathesis/core/errors.py +459 -0
- schemathesis/core/failures.py +315 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
- schemathesis/core/output/__init__.py +46 -0
- schemathesis/core/output/sanitization.py +54 -0
- schemathesis/{throttling.py → core/rate_limit.py} +16 -17
- schemathesis/core/registries.py +31 -0
- schemathesis/core/transforms.py +113 -0
- schemathesis/core/transport.py +223 -0
- schemathesis/core/validation.py +54 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +28 -0
- schemathesis/engine/context.py +118 -0
- schemathesis/engine/control.py +36 -0
- schemathesis/engine/core.py +169 -0
- schemathesis/engine/errors.py +464 -0
- schemathesis/engine/events.py +258 -0
- schemathesis/engine/phases/__init__.py +88 -0
- schemathesis/{runner → engine/phases}/probes.py +52 -68
- schemathesis/engine/phases/stateful/__init__.py +68 -0
- schemathesis/engine/phases/stateful/_executor.py +356 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +212 -0
- schemathesis/engine/phases/unit/_executor.py +416 -0
- schemathesis/engine/phases/unit/_pool.py +82 -0
- schemathesis/engine/recorder.py +247 -0
- schemathesis/errors.py +43 -0
- schemathesis/filters.py +17 -98
- schemathesis/generation/__init__.py +5 -33
- schemathesis/generation/case.py +317 -0
- schemathesis/generation/coverage.py +282 -175
- schemathesis/generation/hypothesis/__init__.py +36 -0
- schemathesis/generation/hypothesis/builder.py +800 -0
- schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +14 -0
- schemathesis/generation/hypothesis/strategies.py +16 -0
- schemathesis/generation/meta.py +115 -0
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +20 -0
- schemathesis/generation/overrides.py +116 -0
- schemathesis/generation/stateful/__init__.py +37 -0
- schemathesis/generation/stateful/state_machine.py +278 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +284 -0
- schemathesis/hooks.py +80 -101
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +455 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +72 -0
- schemathesis/openapi/loaders.py +313 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +281 -0
- schemathesis/pytest/loaders.py +36 -0
- schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +128 -108
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +537 -273
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +1 -2
- schemathesis/specs/graphql/scalars.py +42 -6
- schemathesis/specs/graphql/schemas.py +141 -137
- schemathesis/specs/graphql/validation.py +11 -17
- schemathesis/specs/openapi/__init__.py +6 -1
- schemathesis/specs/openapi/_cache.py +1 -2
- schemathesis/specs/openapi/_hypothesis.py +142 -156
- schemathesis/specs/openapi/checks.py +368 -257
- schemathesis/specs/openapi/converter.py +4 -4
- schemathesis/specs/openapi/definitions.py +1 -1
- schemathesis/specs/openapi/examples.py +23 -21
- schemathesis/specs/openapi/expressions/__init__.py +31 -19
- schemathesis/specs/openapi/expressions/extractors.py +1 -4
- schemathesis/specs/openapi/expressions/lexer.py +1 -1
- schemathesis/specs/openapi/expressions/nodes.py +36 -41
- schemathesis/specs/openapi/expressions/parser.py +1 -1
- schemathesis/specs/openapi/formats.py +35 -7
- schemathesis/specs/openapi/media_types.py +53 -12
- schemathesis/specs/openapi/negative/__init__.py +7 -4
- schemathesis/specs/openapi/negative/mutations.py +6 -5
- schemathesis/specs/openapi/parameters.py +7 -10
- schemathesis/specs/openapi/patterns.py +94 -31
- schemathesis/specs/openapi/references.py +12 -53
- schemathesis/specs/openapi/schemas.py +233 -307
- schemathesis/specs/openapi/security.py +1 -1
- schemathesis/specs/openapi/serialization.py +12 -6
- schemathesis/specs/openapi/stateful/__init__.py +268 -133
- schemathesis/specs/openapi/stateful/control.py +87 -0
- schemathesis/specs/openapi/stateful/links.py +209 -0
- schemathesis/transport/__init__.py +142 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +124 -0
- schemathesis/transport/requests.py +244 -0
- schemathesis/{_xml.py → transport/serialization.py} +69 -11
- schemathesis/transport/wsgi.py +171 -0
- schemathesis-4.0.0.dist-info/METADATA +204 -0
- schemathesis-4.0.0.dist-info/RECORD +164 -0
- {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/entry_points.txt +1 -1
- {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/licenses/LICENSE +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -19
- schemathesis/_hypothesis.py +0 -717
- schemathesis/_override.py +0 -50
- schemathesis/_patches.py +0 -21
- schemathesis/_rate_limiter.py +0 -7
- schemathesis/cli/callbacks.py +0 -466
- schemathesis/cli/cassettes.py +0 -561
- schemathesis/cli/context.py +0 -75
- schemathesis/cli/debug.py +0 -27
- schemathesis/cli/handlers.py +0 -19
- schemathesis/cli/junitxml.py +0 -124
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -920
- schemathesis/cli/output/short.py +0 -59
- schemathesis/cli/reporting.py +0 -79
- schemathesis/cli/sanitization.py +0 -26
- schemathesis/code_samples.py +0 -151
- schemathesis/constants.py +0 -54
- schemathesis/contrib/__init__.py +0 -11
- schemathesis/contrib/openapi/__init__.py +0 -11
- schemathesis/contrib/openapi/fill_missing_examples.py +0 -24
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -16
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -571
- schemathesis/experimental/__init__.py +0 -109
- schemathesis/extra/_aiohttp.py +0 -28
- schemathesis/extra/_flask.py +0 -13
- schemathesis/extra/_server.py +0 -18
- schemathesis/failures.py +0 -284
- schemathesis/fixups/__init__.py +0 -37
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -28
- schemathesis/generation/_methods.py +0 -44
- schemathesis/graphql.py +0 -3
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/checks.py +0 -86
- schemathesis/internal/copy.py +0 -32
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -37
- schemathesis/internal/diff.py +0 -15
- schemathesis/internal/extensions.py +0 -27
- schemathesis/internal/jsonschema.py +0 -36
- schemathesis/internal/output.py +0 -68
- schemathesis/internal/transformation.py +0 -26
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -474
- schemathesis/loaders.py +0 -122
- schemathesis/models.py +0 -1341
- schemathesis/parameters.py +0 -90
- schemathesis/runner/__init__.py +0 -605
- schemathesis/runner/events.py +0 -389
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/context.py +0 -88
- schemathesis/runner/impl/core.py +0 -1280
- schemathesis/runner/impl/solo.py +0 -80
- schemathesis/runner/impl/threadpool.py +0 -391
- schemathesis/runner/serialization.py +0 -544
- schemathesis/sanitization.py +0 -252
- schemathesis/serializers.py +0 -328
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -202
- schemathesis/service/client.py +0 -133
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -61
- schemathesis/service/extensions.py +0 -224
- schemathesis/service/hosts.py +0 -111
- schemathesis/service/metadata.py +0 -71
- schemathesis/service/models.py +0 -258
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -173
- schemathesis/service/usage.py +0 -66
- schemathesis/specs/graphql/loaders.py +0 -364
- schemathesis/specs/openapi/expressions/context.py +0 -16
- schemathesis/specs/openapi/links.py +0 -389
- schemathesis/specs/openapi/loaders.py +0 -707
- schemathesis/specs/openapi/stateful/statistic.py +0 -198
- schemathesis/specs/openapi/stateful/types.py +0 -14
- schemathesis/specs/openapi/validation.py +0 -26
- schemathesis/stateful/__init__.py +0 -147
- schemathesis/stateful/config.py +0 -97
- schemathesis/stateful/context.py +0 -135
- schemathesis/stateful/events.py +0 -274
- schemathesis/stateful/runner.py +0 -309
- schemathesis/stateful/sink.py +0 -68
- schemathesis/stateful/state_machine.py +0 -328
- schemathesis/stateful/statistic.py +0 -22
- schemathesis/stateful/validation.py +0 -100
- schemathesis/targets.py +0 -77
- schemathesis/transports/__init__.py +0 -369
- schemathesis/transports/asgi.py +0 -7
- schemathesis/transports/auth.py +0 -38
- schemathesis/transports/headers.py +0 -36
- schemathesis/transports/responses.py +0 -57
- schemathesis/types.py +0 -44
- schemathesis/utils.py +0 -164
- schemathesis-3.39.16.dist-info/METADATA +0 -293
- schemathesis-3.39.16.dist-info/RECORD +0 -160
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
- /schemathesis/{internal → core}/result.py +0 -0
- {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/WHEEL +0 -0
schemathesis/checks.py
CHANGED
@@ -1,80 +1,189 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import json
|
4
|
-
from typing import TYPE_CHECKING
|
5
|
-
|
6
|
-
from . import
|
7
|
-
from .
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
status_code_conformance,
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Optional
|
5
|
+
|
6
|
+
from schemathesis.config import ChecksConfig
|
7
|
+
from schemathesis.core.failures import (
|
8
|
+
CustomFailure,
|
9
|
+
Failure,
|
10
|
+
FailureGroup,
|
11
|
+
MalformedJson,
|
12
|
+
ResponseTimeExceeded,
|
13
|
+
ServerError,
|
15
14
|
)
|
15
|
+
from schemathesis.core.registries import Registry
|
16
|
+
from schemathesis.core.transport import Response
|
17
|
+
from schemathesis.generation.overrides import Override
|
16
18
|
|
17
19
|
if TYPE_CHECKING:
|
18
|
-
from .
|
19
|
-
from .models import Case
|
20
|
-
from .transports.responses import GenericResponse
|
20
|
+
from requests.models import CaseInsensitiveDict
|
21
21
|
|
22
|
+
from schemathesis.engine.recorder import ScenarioRecorder
|
23
|
+
from schemathesis.generation.case import Case
|
22
24
|
|
23
|
-
|
24
|
-
"""A check to verify that the response is not a server-side error."""
|
25
|
-
from .specs.graphql.schemas import GraphQLCase
|
26
|
-
from .specs.graphql.validation import validate_graphql_response
|
27
|
-
from .transports.responses import get_json
|
25
|
+
CheckFunction = Callable[["CheckContext", "Response", "Case"], Optional[bool]]
|
28
26
|
|
29
|
-
status_code = response.status_code
|
30
|
-
if status_code >= 500:
|
31
|
-
exc_class = get_server_error(case.operation.verbose_name, status_code)
|
32
|
-
raise exc_class(failures.ServerError.title, context=failures.ServerError(status_code=status_code))
|
33
|
-
if isinstance(case, GraphQLCase):
|
34
|
-
try:
|
35
|
-
data = get_json(response)
|
36
|
-
validate_graphql_response(data)
|
37
|
-
except json.JSONDecodeError as exc:
|
38
|
-
exc_class = get_response_parsing_error(case.operation.verbose_name, exc)
|
39
|
-
context = failures.JSONDecodeErrorContext.from_exception(exc)
|
40
|
-
raise exc_class(context.title, context=context) from exc
|
41
|
-
return None
|
42
27
|
|
28
|
+
class CheckContext:
|
29
|
+
"""Runtime context passed to validation check functions during API testing.
|
43
30
|
|
44
|
-
|
45
|
-
|
31
|
+
Provides access to configuration for currently checked endpoint.
|
32
|
+
"""
|
46
33
|
|
34
|
+
_override: Override | None
|
35
|
+
_auth: tuple[str, str] | None
|
36
|
+
_headers: CaseInsensitiveDict | None
|
37
|
+
config: ChecksConfig
|
38
|
+
"""Configuration settings for validation checks."""
|
39
|
+
_transport_kwargs: dict[str, Any] | None
|
40
|
+
_recorder: ScenarioRecorder | None
|
41
|
+
_checks: list[CheckFunction]
|
42
|
+
|
43
|
+
__slots__ = ("_override", "_auth", "_headers", "config", "_transport_kwargs", "_recorder", "_checks")
|
44
|
+
|
45
|
+
def __init__(
|
46
|
+
self,
|
47
|
+
override: Override | None,
|
48
|
+
auth: tuple[str, str] | None,
|
49
|
+
headers: CaseInsensitiveDict | None,
|
50
|
+
config: ChecksConfig,
|
51
|
+
transport_kwargs: dict[str, Any] | None,
|
52
|
+
recorder: ScenarioRecorder | None = None,
|
53
|
+
) -> None:
|
54
|
+
self._override = override
|
55
|
+
self._auth = auth
|
56
|
+
self._headers = headers
|
57
|
+
self.config = config
|
58
|
+
self._transport_kwargs = transport_kwargs
|
59
|
+
self._recorder = recorder
|
60
|
+
self._checks = []
|
61
|
+
for check in CHECKS.get_all():
|
62
|
+
name = check.__name__
|
63
|
+
if self.config.get_by_name(name=name).enabled:
|
64
|
+
self._checks.append(check)
|
65
|
+
if self.config.max_response_time.enabled:
|
66
|
+
self._checks.append(max_response_time)
|
67
|
+
|
68
|
+
def _find_parent(self, *, case_id: str) -> Case | None:
|
69
|
+
if self._recorder is not None:
|
70
|
+
return self._recorder.find_parent(case_id=case_id)
|
71
|
+
return None
|
72
|
+
|
73
|
+
def _find_related(self, *, case_id: str) -> Iterator[Case]:
|
74
|
+
if self._recorder is not None:
|
75
|
+
yield from self._recorder.find_related(case_id=case_id)
|
76
|
+
|
77
|
+
def _find_response(self, *, case_id: str) -> Response | None:
|
78
|
+
if self._recorder is not None:
|
79
|
+
return self._recorder.find_response(case_id=case_id)
|
80
|
+
return None
|
81
|
+
|
82
|
+
def _record_case(self, *, parent_id: str, case: Case) -> None:
|
83
|
+
if self._recorder is not None:
|
84
|
+
self._recorder.record_case(parent_id=parent_id, transition=None, case=case)
|
85
|
+
|
86
|
+
def _record_response(self, *, case_id: str, response: Response) -> None:
|
87
|
+
if self._recorder is not None:
|
88
|
+
self._recorder.record_response(case_id=case_id, response=response)
|
89
|
+
|
90
|
+
|
91
|
+
CHECKS = Registry[CheckFunction]()
|
92
|
+
|
93
|
+
|
94
|
+
def load_all_checks() -> None:
|
95
|
+
# NOTE: Trigger registering all Open API checks
|
96
|
+
from schemathesis.specs.openapi.checks import status_code_conformance # noqa: F401, F403
|
97
|
+
|
98
|
+
|
99
|
+
def check(func: CheckFunction) -> CheckFunction:
|
100
|
+
"""Register a custom validation check to run against API responses.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
func: Function that takes `(ctx: CheckContext, response: Response, case: Case)` and raises `AssertionError` on validation failure
|
104
|
+
|
105
|
+
Example:
|
106
|
+
```python
|
107
|
+
import schemathesis
|
47
108
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
negative_data_rejection,
|
55
|
-
ignored_auth,
|
56
|
-
)
|
57
|
-
ALL_CHECKS: tuple[CheckFunction, ...] = DEFAULT_CHECKS + OPTIONAL_CHECKS
|
109
|
+
@schemathesis.check
|
110
|
+
def check_cors_headers(ctx, response, case):
|
111
|
+
\"\"\"Verify CORS headers are present\"\"\"
|
112
|
+
if "Access-Control-Allow-Origin" not in response.headers:
|
113
|
+
raise AssertionError("Missing CORS headers")
|
114
|
+
```
|
58
115
|
|
116
|
+
"""
|
117
|
+
return CHECKS.register(func)
|
59
118
|
|
60
|
-
def register(check: CheckFunction) -> CheckFunction:
|
61
|
-
"""Register a new check for schemathesis CLI.
|
62
119
|
|
63
|
-
|
120
|
+
@check
|
121
|
+
def not_a_server_error(ctx: CheckContext, response: Response, case: Case) -> bool | None:
|
122
|
+
"""A check to verify that the response is not a server-side error."""
|
123
|
+
from schemathesis.specs.graphql.schemas import GraphQLSchema
|
124
|
+
from schemathesis.specs.graphql.validation import validate_graphql_response
|
125
|
+
from schemathesis.specs.openapi.utils import expand_status_codes
|
64
126
|
|
65
|
-
|
127
|
+
expected_statuses = expand_status_codes(ctx.config.not_a_server_error.expected_statuses or [])
|
128
|
+
|
129
|
+
status_code = response.status_code
|
130
|
+
if status_code not in expected_statuses:
|
131
|
+
raise ServerError(operation=case.operation.label, status_code=status_code)
|
132
|
+
if isinstance(case.operation.schema, GraphQLSchema):
|
133
|
+
try:
|
134
|
+
data = response.json()
|
135
|
+
validate_graphql_response(case, data)
|
136
|
+
except json.JSONDecodeError as exc:
|
137
|
+
raise MalformedJson.from_exception(operation=case.operation.label, exc=exc) from None
|
138
|
+
return None
|
66
139
|
|
67
|
-
@schemathesis.check
|
68
|
-
def new_check(ctx, response, case):
|
69
|
-
# some awesome assertions!
|
70
|
-
...
|
71
|
-
"""
|
72
|
-
from . import cli
|
73
|
-
from .internal.checks import wrap_check
|
74
140
|
|
75
|
-
|
76
|
-
global ALL_CHECKS
|
141
|
+
DEFAULT_MAX_RESPONSE_TIME = 10.0
|
77
142
|
|
78
|
-
|
79
|
-
|
80
|
-
|
143
|
+
|
144
|
+
def max_response_time(ctx: CheckContext, response: Response, case: Case) -> bool | None:
|
145
|
+
limit = ctx.config.max_response_time.limit or DEFAULT_MAX_RESPONSE_TIME
|
146
|
+
elapsed = response.elapsed
|
147
|
+
if elapsed > limit:
|
148
|
+
raise ResponseTimeExceeded(
|
149
|
+
operation=case.operation.label,
|
150
|
+
message=f"Actual: {elapsed:.2f}ms\nLimit: {limit * 1000:.2f}ms",
|
151
|
+
elapsed=elapsed,
|
152
|
+
deadline=limit,
|
153
|
+
)
|
154
|
+
return None
|
155
|
+
|
156
|
+
|
157
|
+
def run_checks(
|
158
|
+
*,
|
159
|
+
case: Case,
|
160
|
+
response: Response,
|
161
|
+
ctx: CheckContext,
|
162
|
+
checks: Iterable[CheckFunction],
|
163
|
+
on_failure: Callable[[str, set[Failure], Failure], None],
|
164
|
+
on_success: Callable[[str, Case], None] | None = None,
|
165
|
+
) -> set[Failure]:
|
166
|
+
"""Run a set of checks against a response."""
|
167
|
+
collected: set[Failure] = set()
|
168
|
+
|
169
|
+
for check in checks:
|
170
|
+
name = check.__name__
|
171
|
+
try:
|
172
|
+
skip_check = check(ctx, response, case)
|
173
|
+
if not skip_check and on_success:
|
174
|
+
on_success(name, case)
|
175
|
+
except Failure as failure:
|
176
|
+
on_failure(name, collected, failure.with_traceback(None))
|
177
|
+
except AssertionError as exc:
|
178
|
+
custom_failure = CustomFailure(
|
179
|
+
operation=case.operation.label,
|
180
|
+
title=f"Custom check failed: `{name}`",
|
181
|
+
message=str(exc),
|
182
|
+
exception=exc,
|
183
|
+
)
|
184
|
+
on_failure(name, collected, custom_failure)
|
185
|
+
except FailureGroup as group:
|
186
|
+
for sub_failure in group.exceptions:
|
187
|
+
on_failure(name, collected, sub_failure)
|
188
|
+
|
189
|
+
return collected
|