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.
@@ -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
- for status_code in (401, 403):
1058
- if statistic.ratio_for(status_code) >= AUTH_ERRORS_THRESHOLD:
1059
- self.warnings.missing_auth.setdefault(status_code, set()).add(event.recorder.label)
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="Missing authentication",
1233
+ title="Authentication failed",
1224
1234
  operations=self.warnings.missing_auth,
1225
1235
  operation_suffix=" returned authentication errors",
1226
- tips=["💡 Use --auth or -H to provide authentication credentials"],
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" or key == "oneOf":
600
+ elif key == "anyOf":
601
601
  nctx = ctx.with_negative()
602
- # NOTE: Other sub-schemas are not filtered out
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
- yield from cover_schema_iter(nctx, sub_schema, seen)
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) and "Authorization" in excluded_headers:
128
- current_session_auth = session.auth
129
- session.auth = None
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.15
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=zlI0M5PuzduzluzONv3eyuoWWnigPkErPpUtjgHWESE,62476
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=jpl1GpAGZXyoO2aZLUZlIE0h_ygdHMnvUK14oBg51k4,53896
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=1cY3AH-f_AbUGfvfK8WMMKkUxAJT9Iw8eZGyRE2Sd44,8740
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=FSitLbJxjBYU814cqjI_QCkdyoIc-9xfT_1HQcYwsXw,15064
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=rziZTrZCVMAqgy6ldB8iTwhkpAsnjKSgK8hj5Sq3ThE,10656
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.15.dist-info/METADATA,sha256=nQbpPuX0uQ1CRaGdgF7Sor6E9pqtPKFYNHraxONfRdM,8472
161
- schemathesis-4.0.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
162
- schemathesis-4.0.15.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
163
- schemathesis-4.0.15.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
164
- schemathesis-4.0.15.dist-info/RECORD,,
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,,