schemathesis 4.0.15__py3-none-any.whl → 4.0.17__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/cli/commands/run/handlers/output.py +15 -5
- schemathesis/generation/coverage.py +40 -3
- schemathesis/generation/stateful/state_machine.py +3 -1
- schemathesis/specs/openapi/stateful/__init__.py +11 -0
- schemathesis/transport/requests.py +9 -6
- {schemathesis-4.0.15.dist-info → schemathesis-4.0.17.dist-info}/METADATA +1 -1
- {schemathesis-4.0.15.dist-info → schemathesis-4.0.17.dist-info}/RECORD +10 -10
- {schemathesis-4.0.15.dist-info → schemathesis-4.0.17.dist-info}/WHEEL +0 -0
- {schemathesis-4.0.15.dist-info → schemathesis-4.0.17.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.0.15.dist-info → schemathesis-4.0.17.dist-info}/licenses/LICENSE +0 -0
@@ -26,6 +26,7 @@ from schemathesis.engine import Status, events
|
|
26
26
|
from schemathesis.engine.phases import PhaseName, PhaseSkipReason
|
27
27
|
from schemathesis.engine.phases.probes import ProbeOutcome
|
28
28
|
from schemathesis.engine.recorder import Interaction, ScenarioRecorder
|
29
|
+
from schemathesis.generation.meta import CoveragePhaseData
|
29
30
|
from schemathesis.generation.modes import GenerationMode
|
30
31
|
from schemathesis.schemas import ApiStatistic
|
31
32
|
|
@@ -1053,10 +1054,19 @@ class OutputHandler(EventHandler):
|
|
1053
1054
|
|
1054
1055
|
warnings = self.config.warnings_for(operation=operation)
|
1055
1056
|
|
1057
|
+
def has_only_missing_auth_case() -> bool:
|
1058
|
+
case = list(event.recorder.cases.values())[0].value
|
1059
|
+
return bool(
|
1060
|
+
case.meta
|
1061
|
+
and isinstance(case.meta.phase.data, CoveragePhaseData)
|
1062
|
+
and case.meta.phase.data.description == "Missing `Authorization` at header"
|
1063
|
+
)
|
1064
|
+
|
1056
1065
|
if SchemathesisWarning.MISSING_AUTH in warnings:
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1066
|
+
if not (len(event.recorder.cases) == 1 and has_only_missing_auth_case()):
|
1067
|
+
for status_code in (401, 403):
|
1068
|
+
if statistic.ratio_for(status_code) >= AUTH_ERRORS_THRESHOLD:
|
1069
|
+
self.warnings.missing_auth.setdefault(status_code, set()).add(event.recorder.label)
|
1060
1070
|
|
1061
1071
|
# Warn if all positive test cases got 4xx in return and no failure was found
|
1062
1072
|
def all_positive_are_rejected(recorder: ScenarioRecorder) -> bool:
|
@@ -1220,10 +1230,10 @@ class OutputHandler(EventHandler):
|
|
1220
1230
|
click.echo()
|
1221
1231
|
if self.warnings.missing_auth:
|
1222
1232
|
self._display_warning_block(
|
1223
|
-
title="
|
1233
|
+
title="Authentication failed",
|
1224
1234
|
operations=self.warnings.missing_auth,
|
1225
1235
|
operation_suffix=" returned authentication errors",
|
1226
|
-
tips=["💡
|
1236
|
+
tips=["💡 Ensure valid authentication credentials are set via --auth or -H"],
|
1227
1237
|
)
|
1228
1238
|
|
1229
1239
|
if self.warnings.missing_test_data:
|
@@ -597,12 +597,49 @@ def cover_schema_iter(
|
|
597
597
|
with _ignore_unfixable():
|
598
598
|
canonical = canonicalish(schema)
|
599
599
|
yield from cover_schema_iter(nctx, canonical, seen)
|
600
|
-
elif key == "anyOf"
|
600
|
+
elif key == "anyOf":
|
601
601
|
nctx = ctx.with_negative()
|
602
|
-
|
602
|
+
validators = [jsonschema.validators.validator_for(sub_schema)(sub_schema) for sub_schema in value]
|
603
603
|
for idx, sub_schema in enumerate(value):
|
604
604
|
with nctx.at(idx):
|
605
|
-
|
605
|
+
for value in cover_schema_iter(nctx, sub_schema, seen):
|
606
|
+
# Negative value for this schema could be a positive value for another one
|
607
|
+
if is_valid_for_others(value.value, idx, validators):
|
608
|
+
continue
|
609
|
+
yield value
|
610
|
+
elif key == "oneOf":
|
611
|
+
nctx = ctx.with_negative()
|
612
|
+
validators = [jsonschema.validators.validator_for(sub_schema)(sub_schema) for sub_schema in value]
|
613
|
+
for idx, sub_schema in enumerate(value):
|
614
|
+
with nctx.at(idx):
|
615
|
+
for value in cover_schema_iter(nctx, sub_schema, seen):
|
616
|
+
if is_invalid_for_oneOf(value.value, idx, validators):
|
617
|
+
yield value
|
618
|
+
|
619
|
+
|
620
|
+
def is_valid_for_others(value: Any, idx: int, validators: list[jsonschema.Validator]) -> bool:
|
621
|
+
for vidx, validator in enumerate(validators):
|
622
|
+
if idx == vidx:
|
623
|
+
# This one is being negated
|
624
|
+
continue
|
625
|
+
if validator.is_valid(value):
|
626
|
+
return True
|
627
|
+
return False
|
628
|
+
|
629
|
+
|
630
|
+
def is_invalid_for_oneOf(value: Any, idx: int, validators: list[jsonschema.Validator]) -> bool:
|
631
|
+
valid_count = 0
|
632
|
+
for vidx, validator in enumerate(validators):
|
633
|
+
if idx == vidx:
|
634
|
+
# This one is being negated
|
635
|
+
continue
|
636
|
+
if validator.is_valid(value):
|
637
|
+
valid_count += 1
|
638
|
+
# Should circuit - no need to validate more, it is already invalid
|
639
|
+
if valid_count > 1:
|
640
|
+
return True
|
641
|
+
# No matching at all - we successfully generated invalid value
|
642
|
+
return valid_count == 0
|
606
643
|
|
607
644
|
|
608
645
|
def _get_properties(schema: dict | bool) -> dict | bool:
|
@@ -185,9 +185,11 @@ class APIStateMachine(RuleBasedStateMachine):
|
|
185
185
|
if target is not None:
|
186
186
|
super()._add_result_to_targets((target,), result)
|
187
187
|
|
188
|
-
def _add_results_to_targets(self, targets: tuple[str, ...], results: list[StepOutput]) -> None:
|
188
|
+
def _add_results_to_targets(self, targets: tuple[str, ...], results: list[StepOutput | None]) -> None:
|
189
189
|
# Hypothesis >6.131.15
|
190
190
|
for result in results:
|
191
|
+
if result is None:
|
192
|
+
continue
|
191
193
|
target = self._get_target_for_result(result)
|
192
194
|
if target is not None:
|
193
195
|
super()._add_results_to_targets((target,), [result])
|
@@ -4,6 +4,7 @@ from dataclasses import dataclass
|
|
4
4
|
from functools import lru_cache
|
5
5
|
from typing import TYPE_CHECKING, Any, Callable, Iterator
|
6
6
|
|
7
|
+
import jsonschema
|
7
8
|
from hypothesis import strategies as st
|
8
9
|
from hypothesis.stateful import Bundle, Rule, precondition, rule
|
9
10
|
|
@@ -14,6 +15,7 @@ from schemathesis.engine.recorder import ScenarioRecorder
|
|
14
15
|
from schemathesis.generation import GenerationMode
|
15
16
|
from schemathesis.generation.case import Case
|
16
17
|
from schemathesis.generation.hypothesis import strategies
|
18
|
+
from schemathesis.generation.meta import ComponentInfo, ComponentKind
|
17
19
|
from schemathesis.generation.stateful import STATEFUL_TESTS_LABEL
|
18
20
|
from schemathesis.generation.stateful.state_machine import APIStateMachine, StepInput, StepOutput, _normalize_name
|
19
21
|
from schemathesis.schemas import APIOperation
|
@@ -281,6 +283,15 @@ def into_step_input(
|
|
281
283
|
case.body = {**case.body, **new}
|
282
284
|
else:
|
283
285
|
case.body = new
|
286
|
+
if case.meta and case.meta.generation.mode == GenerationMode.NEGATIVE:
|
287
|
+
# It is possible that the new body is now valid and the whole test case could be valid too
|
288
|
+
for alternative in case.operation.body:
|
289
|
+
if alternative.media_type == case.media_type:
|
290
|
+
schema = alternative.as_json_schema(case.operation)
|
291
|
+
if jsonschema.validators.validator_for(schema)(schema).is_valid(new):
|
292
|
+
case.meta.components[ComponentKind.BODY] = ComponentInfo(mode=GenerationMode.POSITIVE)
|
293
|
+
if all(info.mode == GenerationMode.POSITIVE for info in case.meta.components.values()):
|
294
|
+
case.meta.generation.mode = GenerationMode.POSITIVE
|
284
295
|
return StepInput(case=case, transition=transition)
|
285
296
|
|
286
297
|
return inner(output=_output)
|
@@ -80,6 +80,10 @@ class RequestsTransport(BaseTransport["requests.Session"]):
|
|
80
80
|
if cookies is not None:
|
81
81
|
merge_at(data, "cookies", cookies)
|
82
82
|
|
83
|
+
excluded_headers = get_exclude_headers(case)
|
84
|
+
for name in excluded_headers:
|
85
|
+
data["headers"].pop(name, None)
|
86
|
+
|
83
87
|
return data
|
84
88
|
|
85
89
|
def send(self, case: Case, *, session: requests.Session | None = None, **kwargs: Any) -> Response:
|
@@ -112,9 +116,6 @@ class RequestsTransport(BaseTransport["requests.Session"]):
|
|
112
116
|
data.update({key: value for key, value in kwargs.items() if key not in data})
|
113
117
|
data.setdefault("timeout", DEFAULT_RESPONSE_TIMEOUT)
|
114
118
|
|
115
|
-
excluded_headers = get_exclude_headers(case)
|
116
|
-
for name in excluded_headers:
|
117
|
-
data["headers"].pop(name, None)
|
118
119
|
current_session_headers: MutableMapping[str, Any] = {}
|
119
120
|
current_session_auth = None
|
120
121
|
|
@@ -124,9 +125,11 @@ class RequestsTransport(BaseTransport["requests.Session"]):
|
|
124
125
|
close_session = True
|
125
126
|
else:
|
126
127
|
current_session_headers = session.headers
|
127
|
-
if isinstance(session.auth, tuple)
|
128
|
-
|
129
|
-
|
128
|
+
if isinstance(session.auth, tuple):
|
129
|
+
excluded_headers = get_exclude_headers(case)
|
130
|
+
if "Authorization" in excluded_headers:
|
131
|
+
current_session_auth = session.auth
|
132
|
+
session.auth = None
|
130
133
|
close_session = False
|
131
134
|
session.headers = {}
|
132
135
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: schemathesis
|
3
|
-
Version: 4.0.
|
3
|
+
Version: 4.0.17
|
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
|
@@ -23,7 +23,7 @@ schemathesis/cli/commands/run/handlers/__init__.py,sha256=TPZ3KdGi8m0fjlN0GjA31M
|
|
23
23
|
schemathesis/cli/commands/run/handlers/base.py,sha256=yDsTtCiztLksfk7cRzg8JlaAVOfS-zwK3tsJMOXAFyc,530
|
24
24
|
schemathesis/cli/commands/run/handlers/cassettes.py,sha256=rRD4byjp4HXCkJS-zx3jSIFOJsPq77ejPpYeyCtsEZs,19461
|
25
25
|
schemathesis/cli/commands/run/handlers/junitxml.py,sha256=ydk6Ofj-Uti6H8EucT4Snp85cmTA5W7uVpKkoHrIDKE,2586
|
26
|
-
schemathesis/cli/commands/run/handlers/output.py,sha256=
|
26
|
+
schemathesis/cli/commands/run/handlers/output.py,sha256=oS3HP4idz99IxpZcjsqADtEOkuu8wruTcri5c1YIph0,62986
|
27
27
|
schemathesis/cli/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
28
|
schemathesis/cli/ext/fs.py,sha256=3lvoAsEDDdih75ITJJNxemd3nwxX55gGWrI7uDxm0cM,447
|
29
29
|
schemathesis/cli/ext/groups.py,sha256=kQ37t6qeArcKaY2y5VxyK3_KwAkBKCVm58IYV8gewds,2720
|
@@ -85,7 +85,7 @@ schemathesis/engine/phases/unit/_executor.py,sha256=9MmZoKSBVSPk0LWwN3PZ3iaO9nzp
|
|
85
85
|
schemathesis/engine/phases/unit/_pool.py,sha256=iU0hdHDmohPnEv7_S1emcabuzbTf-Cznqwn0pGQ5wNQ,2480
|
86
86
|
schemathesis/generation/__init__.py,sha256=tvNO2FLiY8z3fZ_kL_QJhSgzXfnT4UqwSXMHCwfLI0g,645
|
87
87
|
schemathesis/generation/case.py,sha256=zwAwFQ-Fp7SOxCXYOQyAdwAtNwVJe63PdLpvqackFQY,12296
|
88
|
-
schemathesis/generation/coverage.py,sha256=
|
88
|
+
schemathesis/generation/coverage.py,sha256=2GZIyVfaZ8wAcF1GQohm9pftwNIVeeu5uWhYp8qHKlA,55606
|
89
89
|
schemathesis/generation/meta.py,sha256=adkoMuCfzSjHJ9ZDocQn0GnVldSCkLL3eVR5A_jafwM,2552
|
90
90
|
schemathesis/generation/metrics.py,sha256=cZU5HdeAMcLFEDnTbNE56NuNq4P0N4ew-g1NEz5-kt4,2836
|
91
91
|
schemathesis/generation/modes.py,sha256=Q1fhjWr3zxabU5qdtLvKfpMFZJAwlW9pnxgenjeXTyU,481
|
@@ -97,7 +97,7 @@ schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5
|
|
97
97
|
schemathesis/generation/hypothesis/reporting.py,sha256=uDVow6Ya8YFkqQuOqRsjbzsbyP4KKfr3jA7ZaY4FuKY,279
|
98
98
|
schemathesis/generation/hypothesis/strategies.py,sha256=RurE81E06d99YKG48dizy9346ayfNswYTt38zewmGgw,483
|
99
99
|
schemathesis/generation/stateful/__init__.py,sha256=s7jiJEnguIj44IsRyMi8afs-8yjIUuBbzW58bH5CHjs,1042
|
100
|
-
schemathesis/generation/stateful/state_machine.py,sha256=
|
100
|
+
schemathesis/generation/stateful/state_machine.py,sha256=3oAGpn46adNJx3sp5ym9e30SyYOjJGiEqenDZ5gWtBY,8803
|
101
101
|
schemathesis/graphql/__init__.py,sha256=_eO6MAPHGgiADVGRntnwtPxmuvk666sAh-FAU4cG9-0,326
|
102
102
|
schemathesis/graphql/checks.py,sha256=IADbxiZjgkBWrC5yzHDtohRABX6zKXk5w_zpWNwdzYo,3186
|
103
103
|
schemathesis/graphql/loaders.py,sha256=2tgG4HIvFmjHLr_KexVXnT8hSBM-dKG_fuXTZgE97So,9445
|
@@ -148,17 +148,17 @@ schemathesis/specs/openapi/negative/__init__.py,sha256=1sajF22SrE4pUK7-C6PiseZ9P
|
|
148
148
|
schemathesis/specs/openapi/negative/mutations.py,sha256=xDSUVnGWjuuIcvmW_mJGChf-G-nXst-JBX1okQAzon4,19865
|
149
149
|
schemathesis/specs/openapi/negative/types.py,sha256=a7buCcVxNBG6ILBM3A7oNTAX0lyDseEtZndBuej8MbI,174
|
150
150
|
schemathesis/specs/openapi/negative/utils.py,sha256=ozcOIuASufLqZSgnKUACjX-EOZrrkuNdXX0SDnLoGYA,168
|
151
|
-
schemathesis/specs/openapi/stateful/__init__.py,sha256=
|
151
|
+
schemathesis/specs/openapi/stateful/__init__.py,sha256=t5O84nKXIk_NhFihuSip3EWMLBo4_AG95W_YVbTKPl4,15985
|
152
152
|
schemathesis/specs/openapi/stateful/control.py,sha256=QaXLSbwQWtai5lxvvVtQV3BLJ8n5ePqSKB00XFxp-MA,3695
|
153
153
|
schemathesis/specs/openapi/stateful/links.py,sha256=h5q40jUbcIk5DS_Tih1cvFJxS_QxxG0_9ZQnTs1A_zo,8806
|
154
154
|
schemathesis/transport/__init__.py,sha256=6yg_RfV_9L0cpA6qpbH-SL9_3ggtHQji9CZrpIkbA6s,5321
|
155
155
|
schemathesis/transport/asgi.py,sha256=qTClt6oT_xUEWnRHokACN_uqCNNUZrRPT6YG0PjbElY,926
|
156
156
|
schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEzXd0o,4743
|
157
|
-
schemathesis/transport/requests.py,sha256=
|
157
|
+
schemathesis/transport/requests.py,sha256=46aplzhSmBupegPNMawma-iJWCegWkEd6mzdWLTpgM4,10742
|
158
158
|
schemathesis/transport/serialization.py,sha256=igUXKZ_VJ9gV7P0TUc5PDQBJXl_s0kK9T3ljGWWvo6E,10339
|
159
159
|
schemathesis/transport/wsgi.py,sha256=KoAfvu6RJtzyj24VGB8e-Iaa9smpgXJ3VsM8EgAz2tc,6152
|
160
|
-
schemathesis-4.0.
|
161
|
-
schemathesis-4.0.
|
162
|
-
schemathesis-4.0.
|
163
|
-
schemathesis-4.0.
|
164
|
-
schemathesis-4.0.
|
160
|
+
schemathesis-4.0.17.dist-info/METADATA,sha256=W-ng4zywYAQxqVAdxHO-m6DAHhBnE5jGSgQIhx04uTY,8472
|
161
|
+
schemathesis-4.0.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
162
|
+
schemathesis-4.0.17.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
|
163
|
+
schemathesis-4.0.17.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
|
164
|
+
schemathesis-4.0.17.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|