schemathesis 4.0.16__py3-none-any.whl → 4.0.18__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/generation/coverage.py +40 -3
- schemathesis/generation/hypothesis/builder.py +25 -19
- schemathesis/specs/openapi/converter.py +15 -0
- schemathesis/specs/openapi/patterns.py +5 -0
- schemathesis/specs/openapi/stateful/__init__.py +11 -0
- {schemathesis-4.0.16.dist-info → schemathesis-4.0.18.dist-info}/METADATA +1 -1
- {schemathesis-4.0.16.dist-info → schemathesis-4.0.18.dist-info}/RECORD +10 -10
- {schemathesis-4.0.16.dist-info → schemathesis-4.0.18.dist-info}/WHEEL +0 -0
- {schemathesis-4.0.16.dist-info → schemathesis-4.0.18.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.0.16.dist-info → schemathesis-4.0.18.dist-info}/licenses/LICENSE +0 -0
@@ -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:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
+
import warnings
|
4
5
|
from dataclasses import dataclass
|
5
6
|
from enum import Enum
|
6
7
|
from functools import wraps
|
@@ -311,25 +312,30 @@ def add_coverage(
|
|
311
312
|
for container in LOCATION_TO_CONTAINER.values()
|
312
313
|
if container in as_strategy_kwargs
|
313
314
|
}
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
315
|
+
|
316
|
+
with warnings.catch_warnings():
|
317
|
+
warnings.filterwarnings(
|
318
|
+
"ignore", message=".*but this is not valid syntax for a Python regular expression.*", category=UserWarning
|
319
|
+
)
|
320
|
+
for case in _iter_coverage_cases(
|
321
|
+
operation=operation,
|
322
|
+
generation_modes=generation_modes,
|
323
|
+
generate_duplicate_query_parameters=generate_duplicate_query_parameters,
|
324
|
+
unexpected_methods=unexpected_methods,
|
325
|
+
generation_config=generation_config,
|
326
|
+
):
|
327
|
+
if case.media_type and operation.schema.transport.get_first_matching_media_type(case.media_type) is None:
|
328
|
+
continue
|
329
|
+
adjust_urlencoded_payload(case)
|
330
|
+
auths.set_on_case(case, auth_context, auth_storage)
|
331
|
+
for container_name, value in overrides.items():
|
332
|
+
container = getattr(case, container_name)
|
333
|
+
if container is None:
|
334
|
+
setattr(case, container_name, value)
|
335
|
+
else:
|
336
|
+
container.update(value)
|
337
|
+
|
338
|
+
test = hypothesis.example(case=case)(test)
|
333
339
|
return test
|
334
340
|
|
335
341
|
|
@@ -32,6 +32,21 @@ def to_json_schema(
|
|
32
32
|
schema["format"] = "binary"
|
33
33
|
if update_quantifiers:
|
34
34
|
update_pattern_in_schema(schema)
|
35
|
+
# Sometimes `required` is incorrectly has a boolean value
|
36
|
+
properties = schema.get("properties")
|
37
|
+
if properties:
|
38
|
+
for name, subschema in properties.items():
|
39
|
+
if not isinstance(subschema, dict):
|
40
|
+
continue
|
41
|
+
is_required = subschema.get("required")
|
42
|
+
if is_required is True:
|
43
|
+
schema.setdefault("required", []).append(name)
|
44
|
+
del subschema["required"]
|
45
|
+
elif is_required is False:
|
46
|
+
if "required" in schema and name in schema["required"]:
|
47
|
+
schema["required"].remove(name)
|
48
|
+
del subschema["required"]
|
49
|
+
|
35
50
|
if schema_type == "object":
|
36
51
|
if is_response_schema:
|
37
52
|
# Write-only properties should not occur in responses
|
@@ -81,6 +81,11 @@ def _handle_parsed_pattern(parsed: list, pattern: str, min_length: int | None, m
|
|
81
81
|
inner_pattern = "."
|
82
82
|
else:
|
83
83
|
inner_pattern = pattern[leading_anchor_length:-trailing_anchor_length]
|
84
|
+
# Single literal has the length of 1, but quantifiers could be != 1, which means we can't merge them
|
85
|
+
if op == LITERAL and (
|
86
|
+
(min_length is not None and min_length > 1) or (max_length is not None and max_length < 1)
|
87
|
+
):
|
88
|
+
return pattern
|
84
89
|
return leading_anchor + _update_quantifier(op, value, inner_pattern, min_length, max_length) + trailing_anchor
|
85
90
|
elif (
|
86
91
|
len(parsed) > 3
|
@@ -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)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: schemathesis
|
3
|
-
Version: 4.0.
|
3
|
+
Version: 4.0.18
|
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
|
@@ -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=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
|
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=
|
94
|
+
schemathesis/generation/hypothesis/builder.py,sha256=J5O08kaXV8q1dcjCgjNG7eGbpvlLpqBzl6xWiYZucNc,35111
|
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
|
@@ -126,13 +126,13 @@ schemathesis/specs/openapi/_cache.py,sha256=HpglmETmZU0RCHxp3DO_sg5_B_nzi54Zuw9v
|
|
126
126
|
schemathesis/specs/openapi/_hypothesis.py,sha256=H-4pzT7dECY-AcDGhebKdTSELhGOdyA1WCbZQSMZY3E,22309
|
127
127
|
schemathesis/specs/openapi/checks.py,sha256=0YiMoUy_wsnPvbOrsbnQ2iDxLloNe2-dc5-hnsst0ss,29863
|
128
128
|
schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
|
129
|
-
schemathesis/specs/openapi/converter.py,sha256=
|
129
|
+
schemathesis/specs/openapi/converter.py,sha256=3Fikg9dzOG9XwFiVI0R1KbZioG7Cwc6BOQhPFYItqto,4151
|
130
130
|
schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
|
131
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
134
|
schemathesis/specs/openapi/parameters.py,sha256=ifu_QQCMUzsUHQAkvsOvLuokns6CzesssmQ3Nd3zxII,14594
|
135
|
-
schemathesis/specs/openapi/patterns.py,sha256=
|
135
|
+
schemathesis/specs/openapi/patterns.py,sha256=XhxZzIaCli3E67YZ9V6wQFvBkqDOEVwRrcUSEypjtHU,15890
|
136
136
|
schemathesis/specs/openapi/references.py,sha256=40YcDExPLR2B8EOwt-Csw-5MYFi2xj_DXf91J0Pc9d4,8855
|
137
137
|
schemathesis/specs/openapi/schemas.py,sha256=FXD6yLindLNtjiW5D_GllWxfMAigSKjpjl8_GbqJXxE,52557
|
138
138
|
schemathesis/specs/openapi/security.py,sha256=6UWYMhL-dPtkTineqqBFNKca1i4EuoTduw-EOLeE0aQ,7149
|
@@ -148,7 +148,7 @@ 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
|
@@ -157,8 +157,8 @@ schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEz
|
|
157
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.18.dist-info/METADATA,sha256=KCyTL3AhvoLowCLdICpuDsX3kXYyf7QQ1B7Fh_wb4pY,8472
|
161
|
+
schemathesis-4.0.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
162
|
+
schemathesis-4.0.18.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
|
163
|
+
schemathesis-4.0.18.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
|
164
|
+
schemathesis-4.0.18.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|