schemathesis 4.0.4__py3-none-any.whl → 4.0.5__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 +11 -3
- schemathesis/generation/hypothesis/builder.py +26 -7
- schemathesis/openapi/checks.py +2 -2
- schemathesis/specs/openapi/_hypothesis.py +4 -8
- schemathesis/specs/openapi/checks.py +2 -2
- {schemathesis-4.0.4.dist-info → schemathesis-4.0.5.dist-info}/METADATA +1 -1
- {schemathesis-4.0.4.dist-info → schemathesis-4.0.5.dist-info}/RECORD +10 -10
- {schemathesis-4.0.4.dist-info → schemathesis-4.0.5.dist-info}/WHEEL +0 -0
- {schemathesis-4.0.4.dist-info → schemathesis-4.0.5.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.0.4.dist-info → schemathesis-4.0.5.dist-info}/licenses/LICENSE +0 -0
@@ -111,8 +111,9 @@ class CoverageContext:
|
|
111
111
|
location: str
|
112
112
|
is_required: bool
|
113
113
|
path: list[str | int]
|
114
|
+
custom_formats: dict[str, st.SearchStrategy]
|
114
115
|
|
115
|
-
__slots__ = ("location", "generation_modes", "is_required", "path")
|
116
|
+
__slots__ = ("location", "generation_modes", "is_required", "path", "custom_formats")
|
116
117
|
|
117
118
|
def __init__(
|
118
119
|
self,
|
@@ -121,11 +122,13 @@ class CoverageContext:
|
|
121
122
|
generation_modes: list[GenerationMode] | None = None,
|
122
123
|
is_required: bool,
|
123
124
|
path: list[str | int] | None = None,
|
125
|
+
custom_formats: dict[str, st.SearchStrategy],
|
124
126
|
) -> None:
|
125
127
|
self.location = location
|
126
128
|
self.generation_modes = generation_modes if generation_modes is not None else list(GenerationMode)
|
127
129
|
self.is_required = is_required
|
128
130
|
self.path = path or []
|
131
|
+
self.custom_formats = custom_formats
|
129
132
|
|
130
133
|
@contextmanager
|
131
134
|
def at(self, key: str | int) -> Generator[None, None, None]:
|
@@ -145,6 +148,7 @@ class CoverageContext:
|
|
145
148
|
generation_modes=[GenerationMode.POSITIVE],
|
146
149
|
is_required=self.is_required,
|
147
150
|
path=self.path,
|
151
|
+
custom_formats=self.custom_formats,
|
148
152
|
)
|
149
153
|
|
150
154
|
def with_negative(self) -> CoverageContext:
|
@@ -153,6 +157,7 @@ class CoverageContext:
|
|
153
157
|
generation_modes=[GenerationMode.NEGATIVE],
|
154
158
|
is_required=self.is_required,
|
155
159
|
path=self.path,
|
160
|
+
custom_formats=self.custom_formats,
|
156
161
|
)
|
157
162
|
|
158
163
|
def is_valid_for_location(self, value: Any) -> bool:
|
@@ -234,7 +239,10 @@ class CoverageContext:
|
|
234
239
|
return cached_draw(
|
235
240
|
st.lists(
|
236
241
|
st.fixed_dictionaries(
|
237
|
-
{
|
242
|
+
{
|
243
|
+
key: from_schema(sub_schema, custom_formats=self.custom_formats)
|
244
|
+
for key, sub_schema in items["properties"].items()
|
245
|
+
}
|
238
246
|
),
|
239
247
|
min_size=min_items,
|
240
248
|
)
|
@@ -245,7 +253,7 @@ class CoverageContext:
|
|
245
253
|
if isinstance(schema, dict) and "allOf" not in schema:
|
246
254
|
return self.generate_from_schema(schema)
|
247
255
|
|
248
|
-
return self.generate_from(from_schema(schema))
|
256
|
+
return self.generate_from(from_schema(schema, custom_formats=self.custom_formats))
|
249
257
|
|
250
258
|
|
251
259
|
T = TypeVar("T")
|
@@ -18,7 +18,7 @@ from requests.models import CaseInsensitiveDict
|
|
18
18
|
|
19
19
|
from schemathesis import auths
|
20
20
|
from schemathesis.auths import AuthStorage, AuthStorageMark
|
21
|
-
from schemathesis.config import ProjectConfig
|
21
|
+
from schemathesis.config import GenerationConfig, ProjectConfig
|
22
22
|
from schemathesis.core import NOT_SET, NotSet, SpecificationFeature, media_types
|
23
23
|
from schemathesis.core.errors import InvalidSchema, SerializationNotPossible
|
24
24
|
from schemathesis.core.marks import Mark
|
@@ -183,6 +183,7 @@ def create_test(
|
|
183
183
|
config.as_strategy_kwargs,
|
184
184
|
generate_duplicate_query_parameters=phases_config.coverage.generate_duplicate_query_parameters,
|
185
185
|
unexpected_methods=phases_config.coverage.unexpected_methods,
|
186
|
+
generation_config=generation,
|
186
187
|
)
|
187
188
|
|
188
189
|
setattr(hypothesis_test, SETTINGS_ATTRIBUTE_NAME, settings)
|
@@ -295,7 +296,8 @@ def add_coverage(
|
|
295
296
|
auth_storage: AuthStorage | None,
|
296
297
|
as_strategy_kwargs: dict[str, Any],
|
297
298
|
generate_duplicate_query_parameters: bool,
|
298
|
-
unexpected_methods: set[str]
|
299
|
+
unexpected_methods: set[str],
|
300
|
+
generation_config: GenerationConfig,
|
299
301
|
) -> Callable:
|
300
302
|
from schemathesis.specs.openapi.constants import LOCATION_TO_CONTAINER
|
301
303
|
|
@@ -309,7 +311,11 @@ def add_coverage(
|
|
309
311
|
if container in as_strategy_kwargs
|
310
312
|
}
|
311
313
|
for case in _iter_coverage_cases(
|
312
|
-
operation,
|
314
|
+
operation=operation,
|
315
|
+
generation_modes=generation_modes,
|
316
|
+
generate_duplicate_query_parameters=generate_duplicate_query_parameters,
|
317
|
+
unexpected_methods=unexpected_methods,
|
318
|
+
generation_config=generation_config,
|
313
319
|
):
|
314
320
|
if case.media_type and operation.schema.transport.get_first_matching_media_type(case.media_type) is None:
|
315
321
|
continue
|
@@ -446,11 +452,14 @@ def _stringify_value(val: Any, container_name: str) -> Any:
|
|
446
452
|
|
447
453
|
|
448
454
|
def _iter_coverage_cases(
|
455
|
+
*,
|
449
456
|
operation: APIOperation,
|
450
457
|
generation_modes: list[GenerationMode],
|
451
458
|
generate_duplicate_query_parameters: bool,
|
452
|
-
unexpected_methods: set[str]
|
459
|
+
unexpected_methods: set[str],
|
460
|
+
generation_config: GenerationConfig,
|
453
461
|
) -> Generator[Case, None, None]:
|
462
|
+
from schemathesis.specs.openapi._hypothesis import _build_custom_formats
|
454
463
|
from schemathesis.specs.openapi.constants import LOCATION_TO_CONTAINER
|
455
464
|
from schemathesis.specs.openapi.examples import find_in_responses, find_matching_in_responses
|
456
465
|
from schemathesis.specs.openapi.serialization import get_serializers_for_operation
|
@@ -463,6 +472,7 @@ def _iter_coverage_cases(
|
|
463
472
|
responses = find_in_responses(operation)
|
464
473
|
# NOTE: The HEAD method is excluded
|
465
474
|
unexpected_methods = unexpected_methods or {"get", "put", "post", "delete", "options", "patch", "trace"}
|
475
|
+
custom_formats = _build_custom_formats(generation_config)
|
466
476
|
|
467
477
|
seen_negative = coverage.HashSet()
|
468
478
|
seen_positive = coverage.HashSet()
|
@@ -475,7 +485,10 @@ def _iter_coverage_cases(
|
|
475
485
|
schema.setdefault("examples", []).append(value)
|
476
486
|
gen = coverage.cover_schema_iter(
|
477
487
|
coverage.CoverageContext(
|
478
|
-
location=location,
|
488
|
+
location=location,
|
489
|
+
generation_modes=generation_modes,
|
490
|
+
is_required=parameter.is_required,
|
491
|
+
custom_formats=custom_formats,
|
479
492
|
),
|
480
493
|
schema,
|
481
494
|
)
|
@@ -496,7 +509,10 @@ def _iter_coverage_cases(
|
|
496
509
|
schema.setdefault("examples", []).extend(examples)
|
497
510
|
gen = coverage.cover_schema_iter(
|
498
511
|
coverage.CoverageContext(
|
499
|
-
location="body",
|
512
|
+
location="body",
|
513
|
+
generation_modes=generation_modes,
|
514
|
+
is_required=body.is_required,
|
515
|
+
custom_formats=custom_formats,
|
500
516
|
),
|
501
517
|
schema,
|
502
518
|
)
|
@@ -723,7 +739,10 @@ def _iter_coverage_cases(
|
|
723
739
|
iterator = iter(
|
724
740
|
coverage.cover_schema_iter(
|
725
741
|
coverage.CoverageContext(
|
726
|
-
location=_location,
|
742
|
+
location=_location,
|
743
|
+
generation_modes=[GenerationMode.NEGATIVE],
|
744
|
+
is_required=is_required,
|
745
|
+
custom_formats=custom_formats,
|
727
746
|
),
|
728
747
|
subschema,
|
729
748
|
)
|
schemathesis/openapi/checks.py
CHANGED
@@ -330,7 +330,7 @@ class AcceptedNegativeData(Failure):
|
|
330
330
|
message: str,
|
331
331
|
status_code: int,
|
332
332
|
expected_statuses: list[str],
|
333
|
-
title: str = "
|
333
|
+
title: str = "API accepted schema-violating request",
|
334
334
|
case_id: str | None = None,
|
335
335
|
) -> None:
|
336
336
|
self.operation = operation
|
@@ -358,7 +358,7 @@ class RejectedPositiveData(Failure):
|
|
358
358
|
message: str,
|
359
359
|
status_code: int,
|
360
360
|
allowed_statuses: list[str],
|
361
|
-
title: str = "
|
361
|
+
title: str = "API rejected schema-compliant request",
|
362
362
|
case_id: str | None = None,
|
363
363
|
) -> None:
|
364
364
|
self.operation = operation
|
@@ -423,10 +423,8 @@ def jsonify_python_specific_types(value: dict[str, Any]) -> dict[str, Any]:
|
|
423
423
|
return value
|
424
424
|
|
425
425
|
|
426
|
-
def _build_custom_formats(
|
427
|
-
custom_formats
|
428
|
-
) -> dict[str, st.SearchStrategy]:
|
429
|
-
custom_formats = {**get_default_format_strategies(), **STRING_FORMATS, **(custom_formats or {})}
|
426
|
+
def _build_custom_formats(generation_config: GenerationConfig) -> dict[str, st.SearchStrategy]:
|
427
|
+
custom_formats = {**get_default_format_strategies(), **STRING_FORMATS}
|
430
428
|
if generation_config.exclude_header_characters is not None:
|
431
429
|
custom_formats[HEADER_FORMAT] = header_values(exclude_characters=generation_config.exclude_header_characters)
|
432
430
|
elif not generation_config.allow_x00:
|
@@ -441,7 +439,6 @@ def make_positive_strategy(
|
|
441
439
|
media_type: str | None,
|
442
440
|
generation_config: GenerationConfig,
|
443
441
|
validator_cls: type[jsonschema.protocols.Validator],
|
444
|
-
custom_formats: dict[str, st.SearchStrategy] | None = None,
|
445
442
|
) -> st.SearchStrategy:
|
446
443
|
"""Strategy for generating values that fit the schema."""
|
447
444
|
if is_header_location(location):
|
@@ -451,7 +448,7 @@ def make_positive_strategy(
|
|
451
448
|
for sub_schema in schema.get("properties", {}).values():
|
452
449
|
if list(sub_schema) == ["type"] and sub_schema["type"] == "string":
|
453
450
|
sub_schema.setdefault("format", HEADER_FORMAT)
|
454
|
-
custom_formats = _build_custom_formats(
|
451
|
+
custom_formats = _build_custom_formats(generation_config)
|
455
452
|
return from_schema(
|
456
453
|
schema,
|
457
454
|
custom_formats=custom_formats,
|
@@ -472,9 +469,8 @@ def make_negative_strategy(
|
|
472
469
|
media_type: str | None,
|
473
470
|
generation_config: GenerationConfig,
|
474
471
|
validator_cls: type[jsonschema.protocols.Validator],
|
475
|
-
custom_formats: dict[str, st.SearchStrategy] | None = None,
|
476
472
|
) -> st.SearchStrategy:
|
477
|
-
custom_formats = _build_custom_formats(
|
473
|
+
custom_formats = _build_custom_formats(generation_config)
|
478
474
|
return negative_schema(
|
479
475
|
schema,
|
480
476
|
operation_name=operation_name,
|
@@ -240,7 +240,7 @@ def negative_data_rejection(ctx: CheckContext, response: Response, case: Case) -
|
|
240
240
|
):
|
241
241
|
raise AcceptedNegativeData(
|
242
242
|
operation=case.operation.label,
|
243
|
-
message=f"
|
243
|
+
message=f"Invalid data should have been rejected\nExpected: {', '.join(config.expected_statuses)}",
|
244
244
|
status_code=response.status_code,
|
245
245
|
expected_statuses=config.expected_statuses,
|
246
246
|
)
|
@@ -264,7 +264,7 @@ def positive_data_acceptance(ctx: CheckContext, response: Response, case: Case)
|
|
264
264
|
if case.meta.generation.mode.is_positive and response.status_code not in allowed_statuses:
|
265
265
|
raise RejectedPositiveData(
|
266
266
|
operation=case.operation.label,
|
267
|
-
message=f"
|
267
|
+
message=f"Valid data should have been accepted\nExpected: {', '.join(config.expected_statuses)}",
|
268
268
|
status_code=response.status_code,
|
269
269
|
allowed_statuses=config.expected_statuses,
|
270
270
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: schemathesis
|
3
|
-
Version: 4.0.
|
3
|
+
Version: 4.0.5
|
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=MuqnKsJBpGm2gaqDFdJi1yGSWgBhqJUwtYaX97kfXgo,11820
|
88
|
-
schemathesis/generation/coverage.py,sha256=
|
88
|
+
schemathesis/generation/coverage.py,sha256=gf3kBAQAM5SoUS2k5e4UYIw-w6yvBtm_9KmdQyrhiss,49253
|
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=CURcKG1JamDNwMgZysMaqL5Mrbj0Hx3dgAxJHaB1ZAg,34102
|
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
|
@@ -102,7 +102,7 @@ schemathesis/graphql/__init__.py,sha256=_eO6MAPHGgiADVGRntnwtPxmuvk666sAh-FAU4cG
|
|
102
102
|
schemathesis/graphql/checks.py,sha256=IADbxiZjgkBWrC5yzHDtohRABX6zKXk5w_zpWNwdzYo,3186
|
103
103
|
schemathesis/graphql/loaders.py,sha256=KarhFo2lDiC_1GcC2UGHl_MRDWAMMMWtE7sKrdbaJvo,9348
|
104
104
|
schemathesis/openapi/__init__.py,sha256=-KcsSAM19uOM0N5J4s-yTnQ1BFsptYhW1E51cEf6kVM,311
|
105
|
-
schemathesis/openapi/checks.py,sha256=
|
105
|
+
schemathesis/openapi/checks.py,sha256=VaQRxko6KwZL6saIzc4uUgJa_fj086O7Y6QFK8Zg-7A,12419
|
106
106
|
schemathesis/openapi/loaders.py,sha256=1jh4Me2dngWalBmX5xjfln90tfbH-afxmjsiY1BuUTE,10645
|
107
107
|
schemathesis/openapi/generation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
108
108
|
schemathesis/openapi/generation/filters.py,sha256=pY9cUZdL_kQK80Z2aylTOqqa12zmaYUlYC5BfYgeQMk,2395
|
@@ -123,8 +123,8 @@ schemathesis/specs/graphql/schemas.py,sha256=ezkqgMwx37tMWlhy_I0ahDF1Q44emDSJkyj
|
|
123
123
|
schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzVfHxtdLkCPKM,1422
|
124
124
|
schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
|
125
125
|
schemathesis/specs/openapi/_cache.py,sha256=HpglmETmZU0RCHxp3DO_sg5_B_nzi54Zuw9vGzzYCxY,4295
|
126
|
-
schemathesis/specs/openapi/_hypothesis.py,sha256=
|
127
|
-
schemathesis/specs/openapi/checks.py,sha256=
|
126
|
+
schemathesis/specs/openapi/_hypothesis.py,sha256=usufzl_VyBLgI6riTZ-pGqKnSLndw89GRIuCgCH9QiY,22366
|
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
129
|
schemathesis/specs/openapi/converter.py,sha256=lil8IewM5j8tvt4lpA9g_KITvIwx1M96i45DNSHNjoc,3505
|
130
130
|
schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
|
@@ -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.
|
161
|
-
schemathesis-4.0.
|
162
|
-
schemathesis-4.0.
|
163
|
-
schemathesis-4.0.
|
164
|
-
schemathesis-4.0.
|
160
|
+
schemathesis-4.0.5.dist-info/METADATA,sha256=cIno0cKRp1ZT1ttNzK70yTrErPrGv7479Pc7dRTtDxI,8471
|
161
|
+
schemathesis-4.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
162
|
+
schemathesis-4.0.5.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
|
163
|
+
schemathesis-4.0.5.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
|
164
|
+
schemathesis-4.0.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|