schemathesis 4.0.8__py3-none-any.whl → 4.0.10__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.
@@ -20,10 +20,9 @@ from schemathesis.config import ProjectConfig, ReportFormat, SchemathesisWarning
20
20
  from schemathesis.core.errors import LoaderError, LoaderErrorKind, format_exception, split_traceback
21
21
  from schemathesis.core.failures import MessageBlock, Severity, format_failures
22
22
  from schemathesis.core.output import prepare_response_payload
23
- from schemathesis.core.result import Err, Ok
23
+ from schemathesis.core.result import Ok
24
24
  from schemathesis.core.version import SCHEMATHESIS_VERSION
25
25
  from schemathesis.engine import Status, events
26
- from schemathesis.engine.errors import EngineErrorInfo
27
26
  from schemathesis.engine.phases import PhaseName, PhaseSkipReason
28
27
  from schemathesis.engine.phases.probes import ProbeOutcome
29
28
  from schemathesis.engine.recorder import Interaction, ScenarioRecorder
@@ -960,7 +959,9 @@ class OutputHandler(EventHandler):
960
959
  table.add_row(f"{probe_run.probe.name}:", Text(icon, style=style))
961
960
 
962
961
  message = Padding(table, BLOCK_PADDING)
963
- elif event.status == Status.SKIP:
962
+ else:
963
+ assert event.status == Status.SKIP
964
+ assert isinstance(event.payload, Ok)
964
965
  message = Padding(
965
966
  Text.assemble(
966
967
  ("⏭ ", ""),
@@ -968,17 +969,6 @@ class OutputHandler(EventHandler):
968
969
  ),
969
970
  BLOCK_PADDING,
970
971
  )
971
- else:
972
- assert event.status == Status.ERROR
973
- assert isinstance(event.payload, Err)
974
- error = EngineErrorInfo(event.payload.err())
975
- message = Padding(
976
- Text.assemble(
977
- ("🚫 ", ""),
978
- (f"API probing failed: {error.message}", Style(color="red")),
979
- ),
980
- BLOCK_PADDING,
981
- )
982
972
  self.console.print(message)
983
973
  self.console.print()
984
974
  elif phase.name == PhaseName.STATEFUL_TESTING and phase.is_enabled and self.stateful_tests_manager is not None:
@@ -1337,6 +1327,9 @@ class OutputHandler(EventHandler):
1337
1327
  click.echo(_style("Test Phases:", bold=True))
1338
1328
 
1339
1329
  for phase in PhaseName:
1330
+ if phase == PhaseName.PROBING:
1331
+ # It is not a test phase
1332
+ continue
1340
1333
  status, skip_reason = self.phases[phase]
1341
1334
 
1342
1335
  if status == Status.SKIP:
@@ -426,7 +426,16 @@ def is_unrecoverable_network_error(exc: Exception) -> bool:
426
426
 
427
427
  def has_connection_reset(inner: BaseException) -> bool:
428
428
  exc_str = str(inner)
429
- if any(pattern in exc_str for pattern in ["Connection reset by peer", "[Errno 104]", "ECONNRESET"]):
429
+ if any(
430
+ pattern in exc_str
431
+ for pattern in [
432
+ "Connection aborted",
433
+ "Connection reset by peer",
434
+ "[Errno 104]",
435
+ "ECONNRESET",
436
+ "An established connection was aborted",
437
+ ]
438
+ ):
430
439
  return True
431
440
 
432
441
  if inner.__context__ is not None:
@@ -434,7 +443,7 @@ def is_unrecoverable_network_error(exc: Exception) -> bool:
434
443
 
435
444
  return False
436
445
 
437
- if isinstance(exc, requests.Timeout):
446
+ if isinstance(exc, (requests.Timeout, requests.exceptions.ChunkedEncodingError)):
438
447
  return True
439
448
  if isinstance(exc.__context__, ProtocolError):
440
449
  if len(exc.__context__.args) == 2 and isinstance(exc.__context__.args[1], RemoteDisconnected):
@@ -13,7 +13,7 @@ import warnings
13
13
  from dataclasses import dataclass
14
14
  from typing import TYPE_CHECKING
15
15
 
16
- from schemathesis.core.result import Err, Ok, Result
16
+ from schemathesis.core.result import Ok, Result
17
17
  from schemathesis.core.transport import USER_AGENT
18
18
  from schemathesis.engine import Status, events
19
19
  from schemathesis.transport.prepare import get_default_headers
@@ -41,16 +41,11 @@ def execute(ctx: EngineContext, phase: Phase) -> EventGenerator:
41
41
  payload: Result[ProbePayload, Exception] | None = None
42
42
  for result in probes:
43
43
  if isinstance(result.probe, NullByteInHeader) and result.is_failure:
44
- from ...specs.openapi import formats
45
- from ...specs.openapi.formats import HEADER_FORMAT, header_values
44
+ from schemathesis.specs.openapi import formats
45
+ from schemathesis.specs.openapi.formats import HEADER_FORMAT, header_values
46
46
 
47
47
  formats.register(HEADER_FORMAT, header_values(exclude_characters="\n\r\x00"))
48
- if result.error is not None:
49
- status = Status.ERROR
50
- payload = Err(result.error)
51
- else:
52
- status = Status.SUCCESS
53
- payload = Ok(ProbePayload(probes=probes))
48
+ payload = Ok(ProbePayload(probes=probes))
54
49
  yield events.PhaseFinished(phase=phase, status=status, payload=payload)
55
50
 
56
51
 
@@ -177,6 +177,18 @@ class CoverageContext:
177
177
  return bool(value)
178
178
  return True
179
179
 
180
+ def can_be_negated(self, schema: dict[str, Any]) -> bool:
181
+ # Path, query, header, and cookie parameters will be stringified anyway
182
+ # If there are no constraints, then anything will match the original schema after serialization
183
+ if self.location in ("query", "path", "header", "cookie"):
184
+ cleaned = {
185
+ k: v
186
+ for k, v in schema.items()
187
+ if not k.startswith("x-") and k not in ["description", "example", "examples"]
188
+ }
189
+ return cleaned != {}
190
+ return True
191
+
180
192
  def generate_from(self, strategy: st.SearchStrategy) -> Any:
181
193
  return cached_draw(strategy)
182
194
 
@@ -389,6 +401,8 @@ def cover_schema_iter(
389
401
  yield from _cover_positive_for_type(ctx, schema, ty)
390
402
  if GenerationMode.NEGATIVE in ctx.generation_modes:
391
403
  template = None
404
+ if not ctx.can_be_negated(schema):
405
+ return
392
406
  for key, value in schema.items():
393
407
  with _ignore_unfixable(), ctx.at(key):
394
408
  if key == "enum":
@@ -1086,6 +1100,8 @@ def _negative_type(
1086
1100
  del strategies["integer"]
1087
1101
  if "integer" in types:
1088
1102
  strategies["number"] = FLOAT_STRATEGY.filter(_is_non_integer_float)
1103
+ if ctx.location == "query":
1104
+ strategies.pop("object", None)
1089
1105
  for strategy in strategies.values():
1090
1106
  value = ctx.generate_from(strategy)
1091
1107
  if seen.insert(value) and ctx.is_valid_for_location(value):
@@ -624,7 +624,9 @@ def _iter_coverage_cases(
624
624
  ),
625
625
  )
626
626
  # Generate duplicate query parameters
627
- if generate_duplicate_query_parameters and operation.query:
627
+ # NOTE: if the query schema has no constraints, then we may have no negative test cases at all
628
+ # as they all will match the original schema and therefore will be considered as positive ones
629
+ if generate_duplicate_query_parameters and operation.query and "query" in template:
628
630
  container = template["query"]
629
631
  for parameter in operation.query:
630
632
  instant = Instant()
@@ -656,24 +658,26 @@ def _iter_coverage_cases(
656
658
  name = parameter.name
657
659
  location = parameter.location
658
660
  container_name = LOCATION_TO_CONTAINER[location]
659
- container = template[container_name]
660
- data = template.with_container(
661
- container_name=container_name,
662
- value={k: v for k, v in container.items() if k != name},
663
- generation_mode=GenerationMode.NEGATIVE,
664
- )
665
- yield operation.Case(
666
- **data.kwargs,
667
- _meta=CaseMetadata(
668
- generation=GenerationInfo(time=instant.elapsed, mode=GenerationMode.NEGATIVE),
669
- components=data.components,
670
- phase=PhaseInfo.coverage(
671
- description=f"Missing `{name}` at {location}",
672
- parameter=name,
673
- parameter_location=location,
661
+ # NOTE: if the schema is overly permissive we may not have any negative test cases
662
+ if container_name in template:
663
+ container = template[container_name]
664
+ data = template.with_container(
665
+ container_name=container_name,
666
+ value={k: v for k, v in container.items() if k != name},
667
+ generation_mode=GenerationMode.NEGATIVE,
668
+ )
669
+ yield operation.Case(
670
+ **data.kwargs,
671
+ _meta=CaseMetadata(
672
+ generation=GenerationInfo(time=instant.elapsed, mode=GenerationMode.NEGATIVE),
673
+ components=data.components,
674
+ phase=PhaseInfo.coverage(
675
+ description=f"Missing `{name}` at {location}",
676
+ parameter=name,
677
+ parameter_location=location,
678
+ ),
674
679
  ),
675
- ),
676
- )
680
+ )
677
681
  # Generate combinations for each location
678
682
  for location, parameter_set in [
679
683
  ("query", operation.query),
@@ -206,20 +206,23 @@ def _find_request_body_examples_definition(
206
206
 
207
207
 
208
208
  def extract_inner_examples(
209
- examples: dict[str, Any], unresolved_definition: dict[str, Any]
209
+ examples: dict[str, Any] | list, unresolved_definition: dict[str, Any]
210
210
  ) -> Generator[Any, None, None]:
211
211
  """Extract exact examples values from the `examples` dictionary."""
212
- for name, example in examples.items():
213
- if "$ref" in unresolved_definition[name] and "value" not in example and "externalValue" not in example:
214
- # The example here is a resolved example and should be yielded as is
215
- yield example
216
- if isinstance(example, dict):
217
- if "value" in example:
218
- yield example["value"]
219
- elif "externalValue" in example:
220
- with suppress(requests.RequestException):
221
- # Report a warning if not available?
222
- yield load_external_example(example["externalValue"])
212
+ if isinstance(examples, dict):
213
+ for name, example in examples.items():
214
+ if "$ref" in unresolved_definition[name] and "value" not in example and "externalValue" not in example:
215
+ # The example here is a resolved example and should be yielded as is
216
+ yield example
217
+ if isinstance(example, dict):
218
+ if "value" in example:
219
+ yield example["value"]
220
+ elif "externalValue" in example:
221
+ with suppress(requests.RequestException):
222
+ # Report a warning if not available?
223
+ yield load_external_example(example["externalValue"])
224
+ elif isinstance(examples, list):
225
+ yield from examples
223
226
 
224
227
 
225
228
  @lru_cache
@@ -381,9 +384,12 @@ def find_in_responses(operation: APIOperation) -> dict[str, list[dict[str, Any]]
381
384
  ("x-examples", "x-example"),
382
385
  ):
383
386
  examples = definition.get(examples_field, {})
384
- for example in examples.values():
385
- if "value" in example:
386
- output.setdefault(name, []).append(example["value"])
387
+ if isinstance(examples, dict):
388
+ for example in examples.values():
389
+ if "value" in example:
390
+ output.setdefault(name, []).append(example["value"])
391
+ elif isinstance(examples, list):
392
+ output.setdefault(name, []).extend(examples)
387
393
  if example_field in definition:
388
394
  output.setdefault(name, []).append(definition[example_field])
389
395
  return output
@@ -60,9 +60,11 @@ class OpenAPIParameter(Parameter):
60
60
  # JSON Schema allows `examples` as an array
61
61
  examples = []
62
62
  if self.examples_field in self.definition:
63
- examples.extend(
64
- [example["value"] for example in self.definition[self.examples_field].values() if "value" in example]
65
- )
63
+ container = self.definition[self.examples_field]
64
+ if isinstance(container, dict):
65
+ examples.extend([example["value"] for example in container.values() if "value" in example])
66
+ elif isinstance(container, list):
67
+ examples.extend(container)
66
68
  if self.example_field in self.definition:
67
69
  examples.append(self.definition[self.example_field])
68
70
  schema = self.from_open_api_to_json_schema(operation, self.definition)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 4.0.8
3
+ Version: 4.0.10
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=IRERu8Mir3yi5BgDf40SFwYQbxd0aTrWE9IRkjdO7h4,62808
26
+ schemathesis/cli/commands/run/handlers/output.py,sha256=zlI0M5PuzduzluzONv3eyuoWWnigPkErPpUtjgHWESE,62476
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
@@ -72,11 +72,11 @@ schemathesis/engine/__init__.py,sha256=QaFE-FinaTAaarteADo2RRMJ-Sz6hZB9TzD5KjMin
72
72
  schemathesis/engine/context.py,sha256=x-I9KX6rO6hdCvvN8FEdzIZBqIcNaxdNYHgQjcXbZhM,3931
73
73
  schemathesis/engine/control.py,sha256=FXzP8dxL47j1Giqpy2-Bsr_MdMw9YiATSK_UfpFwDtk,1348
74
74
  schemathesis/engine/core.py,sha256=5jfAqFH0XSD7NVgoSXuUPW-dooItscneAzUNq1RBh1E,5712
75
- schemathesis/engine/errors.py,sha256=HRtFFg-TQ68VmGAM3p6VLOimTU7VaFnv6iKD9-ucjaw,18932
75
+ schemathesis/engine/errors.py,sha256=wbFTOE0pmpU79oOzx1l-ypKG92wdFySmNzrN_KTNizM,19168
76
76
  schemathesis/engine/events.py,sha256=VV6epicFIJnX4c87fVNSd0ibDccX3gryDv52OUGa3FI,6370
77
77
  schemathesis/engine/recorder.py,sha256=K3HfMARrT5mPWXPnYebjjcq5CcsBRhMrtZwEL9_Lvtg,8432
78
78
  schemathesis/engine/phases/__init__.py,sha256=jUIfb_9QoUo4zmJEVU0z70PgXPYjt8CIqp4qP_HlYHg,3146
79
- schemathesis/engine/phases/probes.py,sha256=zvK-uLjVXpvaQ89hNq73WRH_OiIHRWvhvnOkx_C8Iq0,5678
79
+ schemathesis/engine/phases/probes.py,sha256=fhe_1ZPYDCfyzqjeg_0aWK_dhXlsgacE3_VVlbhkwdI,5528
80
80
  schemathesis/engine/phases/stateful/__init__.py,sha256=Lz1rgNqCfUSIz173XqCGsiMuUI5bh4L-RIFexU1-c_Q,2461
81
81
  schemathesis/engine/phases/stateful/_executor.py,sha256=_303Yqflx1iFNTQI2EfjSp_2T21YvzJJgMSazhpv5JQ,15200
82
82
  schemathesis/engine/phases/stateful/context.py,sha256=A7X1SLDOWFpCvFN9IiIeNVZM0emjqatmJL_k9UsO7vM,2946
@@ -85,13 +85,13 @@ 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=MuqnKsJBpGm2gaqDFdJi1yGSWgBhqJUwtYaX97kfXgo,11820
88
- schemathesis/generation/coverage.py,sha256=gf3kBAQAM5SoUS2k5e4UYIw-w6yvBtm_9KmdQyrhiss,49253
88
+ schemathesis/generation/coverage.py,sha256=0d52xHf1HUbilPmCeJlplnw7knSdc0lEv4Hr0HXYUTE,49949
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
92
92
  schemathesis/generation/overrides.py,sha256=OBWqDQPreiliaf2M-oyXppVKHoJkCRzxtwSJx1b6AFw,3759
93
93
  schemathesis/generation/hypothesis/__init__.py,sha256=SVwM-rx07jPZzms0idWYACgUtWAxh49HRuTnaQ__zf0,1549
94
- schemathesis/generation/hypothesis/builder.py,sha256=CURcKG1JamDNwMgZysMaqL5Mrbj0Hx3dgAxJHaB1ZAg,34102
94
+ schemathesis/generation/hypothesis/builder.py,sha256=EPJkeyeirU-pMuD4NGrY1e4HRp0cmBNg_1NLRFRxOfk,34550
95
95
  schemathesis/generation/hypothesis/examples.py,sha256=6eGaKUEC3elmKsaqfKj1sLvM8EHc-PWT4NRBq4NI0Rs,1409
96
96
  schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
97
97
  schemathesis/generation/hypothesis/reporting.py,sha256=uDVow6Ya8YFkqQuOqRsjbzsbyP4KKfr3jA7ZaY4FuKY,279
@@ -128,10 +128,10 @@ schemathesis/specs/openapi/checks.py,sha256=0YiMoUy_wsnPvbOrsbnQ2iDxLloNe2-dc5-h
128
128
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
129
129
  schemathesis/specs/openapi/converter.py,sha256=lil8IewM5j8tvt4lpA9g_KITvIwx1M96i45DNSHNjoc,3505
130
130
  schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
131
- schemathesis/specs/openapi/examples.py,sha256=11GMuwyTws8MizQ3CkfT3dQD4TFP22CGYBMUyESYsDM,21218
131
+ schemathesis/specs/openapi/examples.py,sha256=V1fbsbMto_So7lTWnyGa7f3u9On2br8yZ-cPzcC2-Bw,21542
132
132
  schemathesis/specs/openapi/formats.py,sha256=8AIS7Uey-Z1wm1WYRqnsVqHwG9d316PdqfKLqwUs7us,3516
133
133
  schemathesis/specs/openapi/media_types.py,sha256=F5M6TKl0s6Z5X8mZpPsWDEdPBvxclKRcUOc41eEwKbo,2472
134
- schemathesis/specs/openapi/parameters.py,sha256=BevME4DWLQ-OFvc_7fREMjj99VAbVNxVb5i8OEX6Pfs,14453
134
+ schemathesis/specs/openapi/parameters.py,sha256=ifu_QQCMUzsUHQAkvsOvLuokns6CzesssmQ3Nd3zxII,14594
135
135
  schemathesis/specs/openapi/patterns.py,sha256=cBj8W4wn7VLJd4nABaIH5f502-zBDiqljxLgPWUn-50,15609
136
136
  schemathesis/specs/openapi/references.py,sha256=40YcDExPLR2B8EOwt-Csw-5MYFi2xj_DXf91J0Pc9d4,8855
137
137
  schemathesis/specs/openapi/schemas.py,sha256=4bL-nOFOykfOGHbkmUor9MnCqhAzW5S5oIQbL36wvVc,51972
@@ -157,8 +157,8 @@ schemathesis/transport/prepare.py,sha256=iiB8KTAqnnuqjWzblIPiGVdkGIF7Yr1SAEz-KZz
157
157
  schemathesis/transport/requests.py,sha256=rziZTrZCVMAqgy6ldB8iTwhkpAsnjKSgK8hj5Sq3ThE,10656
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.8.dist-info/METADATA,sha256=drG7CbWUVvqW5yB1wAuNZeU4M8Meq1Bcd0aMag2EZmA,8471
161
- schemathesis-4.0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
162
- schemathesis-4.0.8.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
163
- schemathesis-4.0.8.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
164
- schemathesis-4.0.8.dist-info/RECORD,,
160
+ schemathesis-4.0.10.dist-info/METADATA,sha256=VMiIU9pSV3BpGkj_5Jl9RrgdusrmR9tqLsDTpZCfxnw,8472
161
+ schemathesis-4.0.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
162
+ schemathesis-4.0.10.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
163
+ schemathesis-4.0.10.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
164
+ schemathesis-4.0.10.dist-info/RECORD,,