schemathesis 3.36.3__py3-none-any.whl → 3.36.4__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/cassettes.py +6 -9
- schemathesis/generation/_hypothesis.py +7 -1
- schemathesis/generation/coverage.py +47 -20
- schemathesis/internal/checks.py +2 -1
- schemathesis/specs/openapi/definitions.py +7 -11
- schemathesis/stateful/runner.py +5 -1
- schemathesis/stateful/validation.py +11 -14
- schemathesis/transports/__init__.py +1 -1
- {schemathesis-3.36.3.dist-info → schemathesis-3.36.4.dist-info}/METADATA +1 -1
- {schemathesis-3.36.3.dist-info → schemathesis-3.36.4.dist-info}/RECORD +13 -13
- {schemathesis-3.36.3.dist-info → schemathesis-3.36.4.dist-info}/WHEEL +0 -0
- {schemathesis-3.36.3.dist-info → schemathesis-3.36.4.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.36.3.dist-info → schemathesis-3.36.4.dist-info}/licenses/LICENSE +0 -0
schemathesis/cli/cassettes.py
CHANGED
|
@@ -79,24 +79,18 @@ class CassetteWriter(EventHandler):
|
|
|
79
79
|
def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
|
|
80
80
|
if isinstance(event, events.Initialized):
|
|
81
81
|
# In the beginning we write metadata and start `http_interactions` list
|
|
82
|
-
self.queue.put(Initialize())
|
|
82
|
+
self.queue.put(Initialize(seed=event.seed))
|
|
83
83
|
elif isinstance(event, events.AfterExecution):
|
|
84
|
-
# Seed is always present at this point, the original Optional[int] type is there because `TestResult`
|
|
85
|
-
# instance is created before `seed` is generated on the hypothesis side
|
|
86
|
-
seed = cast(int, event.result.seed)
|
|
87
84
|
self.queue.put(
|
|
88
85
|
Process(
|
|
89
|
-
seed=seed,
|
|
90
86
|
correlation_id=event.correlation_id,
|
|
91
87
|
thread_id=event.thread_id,
|
|
92
88
|
interactions=event.result.interactions,
|
|
93
89
|
)
|
|
94
90
|
)
|
|
95
91
|
elif isinstance(event, events.AfterStatefulExecution):
|
|
96
|
-
seed = cast(int, event.result.seed)
|
|
97
92
|
self.queue.put(
|
|
98
93
|
Process(
|
|
99
|
-
seed=seed,
|
|
100
94
|
# Correlation ID is not used in stateful testing
|
|
101
95
|
correlation_id="",
|
|
102
96
|
thread_id=event.thread_id,
|
|
@@ -118,12 +112,13 @@ class CassetteWriter(EventHandler):
|
|
|
118
112
|
class Initialize:
|
|
119
113
|
"""Start up, the first message to make preparations before proceeding the input data."""
|
|
120
114
|
|
|
115
|
+
seed: int | None
|
|
116
|
+
|
|
121
117
|
|
|
122
118
|
@dataclass
|
|
123
119
|
class Process:
|
|
124
120
|
"""A new chunk of data should be processed."""
|
|
125
121
|
|
|
126
|
-
seed: int
|
|
127
122
|
correlation_id: str
|
|
128
123
|
thread_id: int
|
|
129
124
|
interactions: list[SerializedInteraction]
|
|
@@ -219,9 +214,11 @@ def vcr_writer(file_handle: click.utils.LazyFile, preserve_exact_body_bytes: boo
|
|
|
219
214
|
)
|
|
220
215
|
write_double_quoted(output, string)
|
|
221
216
|
|
|
217
|
+
seed = "null"
|
|
222
218
|
while True:
|
|
223
219
|
item = queue.get()
|
|
224
220
|
if isinstance(item, Initialize):
|
|
221
|
+
seed = f"'{item.seed}'"
|
|
225
222
|
stream.write(
|
|
226
223
|
f"""command: '{get_command_representation()}'
|
|
227
224
|
recorded_with: 'Schemathesis {SCHEMATHESIS_VERSION}'
|
|
@@ -235,7 +232,7 @@ http_interactions:"""
|
|
|
235
232
|
stream.write(
|
|
236
233
|
f"""\n- id: '{current_id}'
|
|
237
234
|
status: '{status}'
|
|
238
|
-
seed:
|
|
235
|
+
seed: {seed}
|
|
239
236
|
thread_id: {item.thread_id}
|
|
240
237
|
correlation_id: '{item.correlation_id}'
|
|
241
238
|
data_generation_method: '{interaction.data_generation_method.value}'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
from functools import lru_cache, reduce
|
|
4
5
|
from operator import or_
|
|
5
6
|
from typing import TYPE_CHECKING, TypeVar
|
|
@@ -8,6 +9,8 @@ if TYPE_CHECKING:
|
|
|
8
9
|
from hypothesis import settings
|
|
9
10
|
from hypothesis import strategies as st
|
|
10
11
|
|
|
12
|
+
SCHEMATHESIS_BENCHMARK_SEED = os.environ.get("SCHEMATHESIS_BENCHMARK_SEED")
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
@lru_cache
|
|
13
16
|
def default_settings() -> settings:
|
|
@@ -33,13 +36,16 @@ def get_single_example(strategy: st.SearchStrategy[T]) -> T: # type: ignore[typ
|
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
def add_single_example(strategy: st.SearchStrategy[T], examples: list[T]) -> None:
|
|
36
|
-
from hypothesis import given
|
|
39
|
+
from hypothesis import given, seed
|
|
37
40
|
|
|
38
41
|
@given(strategy) # type: ignore
|
|
39
42
|
@default_settings() # type: ignore
|
|
40
43
|
def example_generating_inner_function(ex: T) -> None:
|
|
41
44
|
examples.append(ex)
|
|
42
45
|
|
|
46
|
+
if SCHEMATHESIS_BENCHMARK_SEED is not None:
|
|
47
|
+
example_generating_inner_function = seed(SCHEMATHESIS_BENCHMARK_SEED)(example_generating_inner_function)
|
|
48
|
+
|
|
43
49
|
example_generating_inner_function()
|
|
44
50
|
|
|
45
51
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import functools
|
|
4
5
|
from contextlib import contextmanager, suppress
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
+
from dataclasses import dataclass
|
|
6
7
|
from functools import lru_cache
|
|
7
8
|
from itertools import combinations
|
|
8
9
|
from typing import Any, Generator, Iterator, TypeVar, cast
|
|
@@ -18,12 +19,20 @@ from schemathesis.constants import NOT_SET
|
|
|
18
19
|
from ._hypothesis import get_single_example
|
|
19
20
|
from ._methods import DataGenerationMethod
|
|
20
21
|
|
|
22
|
+
|
|
23
|
+
def _replace_zero_with_nonzero(x: float) -> float:
|
|
24
|
+
return x or 0.0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def json_recursive_strategy(strategy: st.SearchStrategy) -> st.SearchStrategy:
|
|
28
|
+
return st.lists(strategy, max_size=3) | st.dictionaries(st.text(), strategy, max_size=3)
|
|
29
|
+
|
|
30
|
+
|
|
21
31
|
BUFFER_SIZE = 8 * 1024
|
|
22
|
-
FLOAT_STRATEGY: st.SearchStrategy = st.floats(allow_nan=False, allow_infinity=False).map(
|
|
32
|
+
FLOAT_STRATEGY: st.SearchStrategy = st.floats(allow_nan=False, allow_infinity=False).map(_replace_zero_with_nonzero)
|
|
23
33
|
NUMERIC_STRATEGY: st.SearchStrategy = st.integers() | FLOAT_STRATEGY
|
|
24
34
|
JSON_STRATEGY: st.SearchStrategy = st.recursive(
|
|
25
|
-
st.none() | st.booleans() | NUMERIC_STRATEGY | st.text(),
|
|
26
|
-
lambda strategy: st.lists(strategy, max_size=3) | st.dictionaries(st.text(), strategy, max_size=3),
|
|
35
|
+
st.none() | st.booleans() | NUMERIC_STRATEGY | st.text(), json_recursive_strategy
|
|
27
36
|
)
|
|
28
37
|
ARRAY_STRATEGY: st.SearchStrategy = st.lists(JSON_STRATEGY)
|
|
29
38
|
OBJECT_STRATEGY: st.SearchStrategy = st.dictionaries(st.text(), JSON_STRATEGY)
|
|
@@ -60,7 +69,14 @@ def cached_draw(strategy: st.SearchStrategy) -> Any:
|
|
|
60
69
|
|
|
61
70
|
@dataclass
|
|
62
71
|
class CoverageContext:
|
|
63
|
-
data_generation_methods: list[DataGenerationMethod]
|
|
72
|
+
data_generation_methods: list[DataGenerationMethod]
|
|
73
|
+
|
|
74
|
+
__slots__ = ("data_generation_methods",)
|
|
75
|
+
|
|
76
|
+
def __init__(self, data_generation_methods: list[DataGenerationMethod] | None = None) -> None:
|
|
77
|
+
self.data_generation_methods = (
|
|
78
|
+
data_generation_methods if data_generation_methods is not None else DataGenerationMethod.all()
|
|
79
|
+
)
|
|
64
80
|
|
|
65
81
|
@classmethod
|
|
66
82
|
def with_positive(cls) -> CoverageContext:
|
|
@@ -70,12 +86,8 @@ class CoverageContext:
|
|
|
70
86
|
def with_negative(cls) -> CoverageContext:
|
|
71
87
|
return CoverageContext(data_generation_methods=[DataGenerationMethod.negative])
|
|
72
88
|
|
|
73
|
-
def generate_from(self, strategy: st.SearchStrategy
|
|
74
|
-
|
|
75
|
-
value = cached_draw(strategy)
|
|
76
|
-
else:
|
|
77
|
-
value = get_single_example(strategy)
|
|
78
|
-
return value
|
|
89
|
+
def generate_from(self, strategy: st.SearchStrategy) -> Any:
|
|
90
|
+
return cached_draw(strategy)
|
|
79
91
|
|
|
80
92
|
def generate_from_schema(self, schema: dict) -> Any:
|
|
81
93
|
return self.generate_from(from_schema(schema))
|
|
@@ -554,9 +566,12 @@ def select_combinations(optional: list[str]) -> Iterator[tuple[str, ...]]:
|
|
|
554
566
|
|
|
555
567
|
|
|
556
568
|
def _negative_enum(ctx: CoverageContext, value: list) -> Generator[GeneratedValue, None, None]:
|
|
557
|
-
|
|
569
|
+
def is_not_in_value(x: Any) -> bool:
|
|
570
|
+
return x not in value
|
|
571
|
+
|
|
572
|
+
strategy = JSON_STRATEGY.filter(is_not_in_value)
|
|
558
573
|
# The exact negative value is not important here
|
|
559
|
-
yield NegativeValue(ctx.generate_from(strategy
|
|
574
|
+
yield NegativeValue(ctx.generate_from(strategy), description="Invalid enum value")
|
|
560
575
|
|
|
561
576
|
|
|
562
577
|
def _negative_properties(
|
|
@@ -573,7 +588,7 @@ def _negative_properties(
|
|
|
573
588
|
|
|
574
589
|
def _negative_pattern(ctx: CoverageContext, pattern: str) -> Generator[GeneratedValue, None, None]:
|
|
575
590
|
yield NegativeValue(
|
|
576
|
-
ctx.generate_from(st.text().filter(
|
|
591
|
+
ctx.generate_from(st.text().filter(pattern.__ne__)),
|
|
577
592
|
description=f"Value not matching the '{pattern}' pattern",
|
|
578
593
|
)
|
|
579
594
|
|
|
@@ -606,19 +621,31 @@ def _negative_required(
|
|
|
606
621
|
)
|
|
607
622
|
|
|
608
623
|
|
|
624
|
+
def _is_invalid_hostname(v: Any) -> bool:
|
|
625
|
+
return v == "" or not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, "hostname")
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def _is_invalid_format(v: Any, format: str) -> bool:
|
|
629
|
+
return not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, format)
|
|
630
|
+
|
|
631
|
+
|
|
609
632
|
def _negative_format(ctx: CoverageContext, schema: dict, format: str) -> Generator[GeneratedValue, None, None]:
|
|
610
633
|
# Hypothesis-jsonschema does not canonicalise it properly right now, which leads to unsatisfiable schema
|
|
611
634
|
without_format = {k: v for k, v in schema.items() if k != "format"}
|
|
612
635
|
without_format.setdefault("type", "string")
|
|
613
636
|
strategy = from_schema(without_format)
|
|
614
637
|
if format in jsonschema.Draft202012Validator.FORMAT_CHECKER.checkers:
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
638
|
+
if format == "hostname":
|
|
639
|
+
strategy = strategy.filter(_is_invalid_hostname)
|
|
640
|
+
else:
|
|
641
|
+
strategy = strategy.filter(functools.partial(_is_invalid_format, format=format))
|
|
619
642
|
yield NegativeValue(ctx.generate_from(strategy), description=f"Value not matching the '{format}' format")
|
|
620
643
|
|
|
621
644
|
|
|
645
|
+
def _is_non_integer_float(x: float) -> bool:
|
|
646
|
+
return x != int(x)
|
|
647
|
+
|
|
648
|
+
|
|
622
649
|
def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Generator[GeneratedValue, None, None]:
|
|
623
650
|
strategies = {
|
|
624
651
|
"integer": st.integers(),
|
|
@@ -638,9 +665,9 @@ def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Gene
|
|
|
638
665
|
if "number" in types:
|
|
639
666
|
del strategies["integer"]
|
|
640
667
|
if "integer" in types:
|
|
641
|
-
strategies["number"] = FLOAT_STRATEGY.filter(
|
|
668
|
+
strategies["number"] = FLOAT_STRATEGY.filter(_is_non_integer_float)
|
|
642
669
|
for strat in strategies.values():
|
|
643
|
-
value = ctx.generate_from(strat
|
|
670
|
+
value = ctx.generate_from(strat)
|
|
644
671
|
hashed = _to_hashable_key(value)
|
|
645
672
|
if hashed in seen:
|
|
646
673
|
continue
|
schemathesis/internal/checks.py
CHANGED
|
@@ -6,6 +6,7 @@ from dataclasses import dataclass
|
|
|
6
6
|
from typing import TYPE_CHECKING, Callable, Optional
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
+
from requests.auth import HTTPDigestAuth
|
|
9
10
|
from requests.structures import CaseInsensitiveDict
|
|
10
11
|
|
|
11
12
|
from ..models import Case
|
|
@@ -23,7 +24,7 @@ class CheckContext:
|
|
|
23
24
|
Provides access to broader test execution data beyond individual test cases.
|
|
24
25
|
"""
|
|
25
26
|
|
|
26
|
-
auth: RawAuth | None = None
|
|
27
|
+
auth: HTTPDigestAuth | RawAuth | None = None
|
|
27
28
|
headers: CaseInsensitiveDict | None = None
|
|
28
29
|
|
|
29
30
|
|
|
@@ -1330,6 +1330,8 @@ OPENAPI_30 = {
|
|
|
1330
1330
|
},
|
|
1331
1331
|
},
|
|
1332
1332
|
}
|
|
1333
|
+
# Generated from the updated schema.yaml from 0035208, which includes unpublished bugfixes
|
|
1334
|
+
# https://github.com/OAI/OpenAPI-Specification/blob/0035208611701b4f7f2c959eb99a8725cca41e6e/schemas/v3.1/schema.yaml
|
|
1333
1335
|
OPENAPI_31 = {
|
|
1334
1336
|
"$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07",
|
|
1335
1337
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -1345,7 +1347,7 @@ OPENAPI_31 = {
|
|
|
1345
1347
|
},
|
|
1346
1348
|
"servers": {"type": "array", "items": {"$ref": "#/$defs/server"}, "default": [{"url": "/"}]},
|
|
1347
1349
|
"paths": {"$ref": "#/$defs/paths"},
|
|
1348
|
-
"webhooks": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item
|
|
1350
|
+
"webhooks": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item"}},
|
|
1349
1351
|
"components": {"$ref": "#/$defs/components"},
|
|
1350
1352
|
"security": {"type": "array", "items": {"$ref": "#/$defs/security-requirement"}},
|
|
1351
1353
|
"tags": {"type": "array", "items": {"$ref": "#/$defs/tag"}},
|
|
@@ -1400,7 +1402,7 @@ OPENAPI_31 = {
|
|
|
1400
1402
|
"$comment": "https://spec.openapis.org/oas/v3.1.0#server-object",
|
|
1401
1403
|
"type": "object",
|
|
1402
1404
|
"properties": {
|
|
1403
|
-
"url": {"type": "string"
|
|
1405
|
+
"url": {"type": "string"},
|
|
1404
1406
|
"description": {"type": "string"},
|
|
1405
1407
|
"variables": {"type": "object", "additionalProperties": {"$ref": "#/$defs/server-variable"}},
|
|
1406
1408
|
},
|
|
@@ -1439,7 +1441,7 @@ OPENAPI_31 = {
|
|
|
1439
1441
|
},
|
|
1440
1442
|
"links": {"type": "object", "additionalProperties": {"$ref": "#/$defs/link-or-reference"}},
|
|
1441
1443
|
"callbacks": {"type": "object", "additionalProperties": {"$ref": "#/$defs/callbacks-or-reference"}},
|
|
1442
|
-
"pathItems": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item
|
|
1444
|
+
"pathItems": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item"}},
|
|
1443
1445
|
},
|
|
1444
1446
|
"patternProperties": {
|
|
1445
1447
|
"^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": {
|
|
@@ -1461,6 +1463,7 @@ OPENAPI_31 = {
|
|
|
1461
1463
|
"$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object",
|
|
1462
1464
|
"type": "object",
|
|
1463
1465
|
"properties": {
|
|
1466
|
+
"$ref": {"type": "string", "format": "uri-reference"},
|
|
1464
1467
|
"summary": {"type": "string"},
|
|
1465
1468
|
"description": {"type": "string"},
|
|
1466
1469
|
"servers": {"type": "array", "items": {"$ref": "#/$defs/server"}},
|
|
@@ -1477,11 +1480,6 @@ OPENAPI_31 = {
|
|
|
1477
1480
|
"$ref": "#/$defs/specification-extensions",
|
|
1478
1481
|
"unevaluatedProperties": False,
|
|
1479
1482
|
},
|
|
1480
|
-
"path-item-or-reference": {
|
|
1481
|
-
"if": {"type": "object", "required": ["$ref"]},
|
|
1482
|
-
"then": {"$ref": "#/$defs/reference"},
|
|
1483
|
-
"else": {"$ref": "#/$defs/path-item"},
|
|
1484
|
-
},
|
|
1485
1483
|
"operation": {
|
|
1486
1484
|
"$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object",
|
|
1487
1485
|
"type": "object",
|
|
@@ -1542,7 +1540,6 @@ OPENAPI_31 = {
|
|
|
1542
1540
|
"if": {"properties": {"in": {"const": "path"}}, "required": ["in"]},
|
|
1543
1541
|
"then": {
|
|
1544
1542
|
"properties": {
|
|
1545
|
-
"name": {"pattern": "[^/#?]+$"},
|
|
1546
1543
|
"style": {"default": "simple", "enum": ["matrix", "label", "simple"]},
|
|
1547
1544
|
"required": {"const": True},
|
|
1548
1545
|
},
|
|
@@ -1662,7 +1659,7 @@ OPENAPI_31 = {
|
|
|
1662
1659
|
"$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object",
|
|
1663
1660
|
"type": "object",
|
|
1664
1661
|
"$ref": "#/$defs/specification-extensions",
|
|
1665
|
-
"additionalProperties": {"$ref": "#/$defs/path-item
|
|
1662
|
+
"additionalProperties": {"$ref": "#/$defs/path-item"},
|
|
1666
1663
|
},
|
|
1667
1664
|
"callbacks-or-reference": {
|
|
1668
1665
|
"if": {"type": "object", "required": ["$ref"]},
|
|
@@ -1755,7 +1752,6 @@ OPENAPI_31 = {
|
|
|
1755
1752
|
"summary": {"type": "string"},
|
|
1756
1753
|
"description": {"type": "string"},
|
|
1757
1754
|
},
|
|
1758
|
-
"unevaluatedProperties": False,
|
|
1759
1755
|
},
|
|
1760
1756
|
"schema": {
|
|
1761
1757
|
"$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object",
|
schemathesis/stateful/runner.py
CHANGED
|
@@ -12,6 +12,7 @@ from hypothesis.control import current_build_context
|
|
|
12
12
|
from hypothesis.errors import Flaky, Unsatisfiable
|
|
13
13
|
|
|
14
14
|
from ..exceptions import CheckFailed
|
|
15
|
+
from ..internal.checks import CheckContext
|
|
15
16
|
from ..targets import TargetMetricCollector
|
|
16
17
|
from . import events
|
|
17
18
|
from .config import StatefulTestRunnerConfig
|
|
@@ -113,6 +114,7 @@ def _execute_state_machine_loop(
|
|
|
113
114
|
) -> None:
|
|
114
115
|
"""Execute the state machine testing loop."""
|
|
115
116
|
from hypothesis import reporting
|
|
117
|
+
from requests.structures import CaseInsensitiveDict
|
|
116
118
|
|
|
117
119
|
from ..transports import RequestsTransport
|
|
118
120
|
|
|
@@ -129,6 +131,7 @@ def _execute_state_machine_loop(
|
|
|
129
131
|
if config.auth is not None:
|
|
130
132
|
session.auth = config.auth
|
|
131
133
|
call_kwargs["session"] = session
|
|
134
|
+
check_ctx = CheckContext(auth=config.auth, headers=CaseInsensitiveDict(config.headers) if config.headers else None)
|
|
132
135
|
|
|
133
136
|
class _InstrumentedStateMachine(state_machine): # type: ignore[valid-type,misc]
|
|
134
137
|
"""State machine with additional hooks for emitting events."""
|
|
@@ -223,7 +226,8 @@ def _execute_state_machine_loop(
|
|
|
223
226
|
validate_response(
|
|
224
227
|
response=response,
|
|
225
228
|
case=case,
|
|
226
|
-
|
|
229
|
+
runner_ctx=ctx,
|
|
230
|
+
check_ctx=check_ctx,
|
|
227
231
|
checks=config.checks,
|
|
228
232
|
additional_checks=additional_checks,
|
|
229
233
|
max_response_time=config.max_response_time,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from ..exceptions import CheckFailed, get_grouped_exception
|
|
6
6
|
from ..internal.checks import CheckContext
|
|
@@ -17,27 +17,24 @@ def validate_response(
|
|
|
17
17
|
*,
|
|
18
18
|
response: GenericResponse,
|
|
19
19
|
case: Case,
|
|
20
|
-
|
|
20
|
+
runner_ctx: RunnerContext,
|
|
21
|
+
check_ctx: CheckContext,
|
|
21
22
|
checks: tuple[CheckFunction, ...],
|
|
22
23
|
additional_checks: tuple[CheckFunction, ...] = (),
|
|
23
24
|
max_response_time: int | None = None,
|
|
24
|
-
headers: dict[str, Any] | None = None,
|
|
25
25
|
) -> None:
|
|
26
26
|
"""Validate the response against the provided checks."""
|
|
27
|
-
from requests.structures import CaseInsensitiveDict
|
|
28
|
-
|
|
29
27
|
from .._compat import MultipleFailures
|
|
30
28
|
from ..checks import _make_max_response_time_failure_message
|
|
31
29
|
from ..failures import ResponseTimeExceeded
|
|
32
30
|
from ..models import Check, Status
|
|
33
31
|
|
|
34
32
|
exceptions: list[CheckFailed | AssertionError] = []
|
|
35
|
-
check_results =
|
|
36
|
-
check_ctx = CheckContext(headers=CaseInsensitiveDict(headers) if headers else None)
|
|
33
|
+
check_results = runner_ctx.checks_for_step
|
|
37
34
|
|
|
38
35
|
def _on_failure(exc: CheckFailed | AssertionError, message: str, context: FailureContext | None) -> None:
|
|
39
36
|
exceptions.append(exc)
|
|
40
|
-
if
|
|
37
|
+
if runner_ctx.is_seen_in_suite(exc):
|
|
41
38
|
return
|
|
42
39
|
failed_check = Check(
|
|
43
40
|
name=name,
|
|
@@ -49,9 +46,9 @@ def validate_response(
|
|
|
49
46
|
context=context,
|
|
50
47
|
request=None,
|
|
51
48
|
)
|
|
52
|
-
|
|
49
|
+
runner_ctx.add_failed_check(failed_check)
|
|
53
50
|
check_results.append(failed_check)
|
|
54
|
-
|
|
51
|
+
runner_ctx.mark_as_seen_in_suite(exc)
|
|
55
52
|
|
|
56
53
|
def _on_passed(_name: str, _case: Case) -> None:
|
|
57
54
|
passed_check = Check(
|
|
@@ -72,16 +69,16 @@ def validate_response(
|
|
|
72
69
|
if not skip_check:
|
|
73
70
|
_on_passed(name, copied_case)
|
|
74
71
|
except CheckFailed as exc:
|
|
75
|
-
if
|
|
72
|
+
if runner_ctx.is_seen_in_run(exc):
|
|
76
73
|
continue
|
|
77
74
|
_on_failure(exc, str(exc), exc.context)
|
|
78
75
|
except AssertionError as exc:
|
|
79
|
-
if
|
|
76
|
+
if runner_ctx.is_seen_in_run(exc):
|
|
80
77
|
continue
|
|
81
78
|
_on_failure(exc, str(exc) or f"Custom check failed: `{name}`", None)
|
|
82
79
|
except MultipleFailures as exc:
|
|
83
80
|
for subexc in exc.exceptions:
|
|
84
|
-
if
|
|
81
|
+
if runner_ctx.is_seen_in_run(subexc):
|
|
85
82
|
continue
|
|
86
83
|
_on_failure(subexc, str(subexc), subexc.context)
|
|
87
84
|
|
|
@@ -93,7 +90,7 @@ def validate_response(
|
|
|
93
90
|
try:
|
|
94
91
|
raise AssertionError(message)
|
|
95
92
|
except AssertionError as _exc:
|
|
96
|
-
if not
|
|
93
|
+
if not runner_ctx.is_seen_in_run(_exc):
|
|
97
94
|
_on_failure(_exc, message, context)
|
|
98
95
|
else:
|
|
99
96
|
_on_passed("max_response_time", case)
|
|
@@ -7,7 +7,7 @@ from contextlib import contextmanager
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from datetime import timedelta
|
|
9
9
|
from inspect import iscoroutinefunction
|
|
10
|
-
from typing import TYPE_CHECKING, Any, Generator,
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Generator, Protocol, TypeVar, cast
|
|
11
11
|
from urllib.parse import urlparse
|
|
12
12
|
|
|
13
13
|
from .. import failures
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: schemathesis
|
|
3
|
-
Version: 3.36.
|
|
3
|
+
Version: 3.36.4
|
|
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://schemathesis.readthedocs.io/en/stable/changelog.html
|
|
@@ -30,7 +30,7 @@ schemathesis/utils.py,sha256=8RkTZ9Ft5IUaGkxABhh34oU7WO2ouMsfgtvFPTx9alI,4875
|
|
|
30
30
|
schemathesis/cli/__init__.py,sha256=OC6QO38QDf55DTIVwrWiQKz8BfTD5QcK574m67NCE2w,72862
|
|
31
31
|
schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
|
|
32
32
|
schemathesis/cli/callbacks.py,sha256=grMKlx_iGiJA4oJsYt_q8l354Y8Nb11IBvOKwbD0jOA,15192
|
|
33
|
-
schemathesis/cli/cassettes.py,sha256=
|
|
33
|
+
schemathesis/cli/cassettes.py,sha256=jD1JTkkEALUUEyzyuJ-KuxgntfGodILUuOu3C9HKjIw,19412
|
|
34
34
|
schemathesis/cli/constants.py,sha256=wk-0GsoJIel8wFFerQ6Kf_6eAYUtIWkwMFwyAqv3yj4,1635
|
|
35
35
|
schemathesis/cli/context.py,sha256=j_lvYQiPa6Q7P4P_IGCM9V2y2gJSpDbpxIIzR5oFB2I,2567
|
|
36
36
|
schemathesis/cli/debug.py,sha256=_YA-bX1ujHl4bqQDEum7M-I2XHBTEGbvgkhvcvKhmgU,658
|
|
@@ -58,11 +58,11 @@ schemathesis/fixups/__init__.py,sha256=RP5QYJVJhp8LXjhH89fCRaIVU26dHCy74jD9seoYM
|
|
|
58
58
|
schemathesis/fixups/fast_api.py,sha256=mn-KzBqnR8jl4W5fY-_ZySabMDMUnpzCIESMHnlvE1c,1304
|
|
59
59
|
schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZETA,745
|
|
60
60
|
schemathesis/generation/__init__.py,sha256=29Zys_tD6kfngaC4zHeC6TOBZQcmo7CWm7KDSYsHStQ,1581
|
|
61
|
-
schemathesis/generation/_hypothesis.py,sha256=
|
|
61
|
+
schemathesis/generation/_hypothesis.py,sha256=Aaol5w3TEOVZn8znrnnJCYfrll8eALW0dCRtz3k0Eis,1661
|
|
62
62
|
schemathesis/generation/_methods.py,sha256=jCK09f4sedDfePrS-6BIiE-CcEE8fJ4ZHxq1BHoTltQ,1101
|
|
63
|
-
schemathesis/generation/coverage.py,sha256=
|
|
63
|
+
schemathesis/generation/coverage.py,sha256=rq7em0ifTQMZxprBE4NIJWFiV6PquKFcnMMSuR2-ohI,28268
|
|
64
64
|
schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
|
|
65
|
-
schemathesis/internal/checks.py,sha256=
|
|
65
|
+
schemathesis/internal/checks.py,sha256=qxJ5Ndeiveh_BT0QZq0Vbv72-ZTpbevOY48XKGQC9Ec,1730
|
|
66
66
|
schemathesis/internal/copy.py,sha256=DcL56z-d69kKR_5u8mlHvjSL1UTyUKNMAwexrwHFY1s,1031
|
|
67
67
|
schemathesis/internal/datetime.py,sha256=zPLBL0XXLNfP-KYel3H2m8pnsxjsA_4d-zTOhJg2EPQ,136
|
|
68
68
|
schemathesis/internal/deprecation.py,sha256=Ty5VBFBlufkITpP0WWTPIPbnB7biDi0kQgXVYWZp820,1273
|
|
@@ -108,7 +108,7 @@ schemathesis/specs/openapi/_hypothesis.py,sha256=Ym1d3GXlabOSbDk_AEkmkZGl9EMIDpu
|
|
|
108
108
|
schemathesis/specs/openapi/checks.py,sha256=-4qOzkova0e4QSqdgsoUiOv2bg57HZmzbpAiAeotc3Q,22288
|
|
109
109
|
schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
|
|
110
110
|
schemathesis/specs/openapi/converter.py,sha256=NkrzBNjtmVwQTeE73NOtwB_puvQTjxxqqrc7gD_yscc,3241
|
|
111
|
-
schemathesis/specs/openapi/definitions.py,sha256=
|
|
111
|
+
schemathesis/specs/openapi/definitions.py,sha256=WTkWwCgTc3OMxfKsqh6YDoGfZMTThSYrHGp8h0vLAK0,93935
|
|
112
112
|
schemathesis/specs/openapi/examples.py,sha256=FwhPWca7bpdHpUp_LRoK09DVgusojO3aXXhXYrK373I,20354
|
|
113
113
|
schemathesis/specs/openapi/formats.py,sha256=JmmkQWNAj5XreXb7Edgj4LADAf4m86YulR_Ec8evpJ4,1220
|
|
114
114
|
schemathesis/specs/openapi/links.py,sha256=a8JmWM9aZhrR5CfyIh6t2SkfonMLfYKOScXY2XlZYN0,17749
|
|
@@ -140,19 +140,19 @@ schemathesis/stateful/__init__.py,sha256=HBg-h131EI8IipHQgufSaXe-CrFTKmffPVsoEFj
|
|
|
140
140
|
schemathesis/stateful/config.py,sha256=huYzqDoD6x20p_VNAR79NgxPwUFO8UXoc3_z4BEuHqU,3586
|
|
141
141
|
schemathesis/stateful/context.py,sha256=vJ9nxTTjI5wo7A6PBGCvVVO_7y-ELs3XERi9PxLzykA,5085
|
|
142
142
|
schemathesis/stateful/events.py,sha256=CyYvyQebOaeTn6UevaB7HXOrUhxCWbqXMfQ7pZK7fV8,6727
|
|
143
|
-
schemathesis/stateful/runner.py,sha256=
|
|
143
|
+
schemathesis/stateful/runner.py,sha256=e3vvRrx0NwN20RdC0cC4mZjcsYezwkhoDwE1cCVOAlw,12615
|
|
144
144
|
schemathesis/stateful/sink.py,sha256=bHYlgh-fMwg1Srxk_XGs0-WV34YccotwH9PGrxCK57A,2474
|
|
145
145
|
schemathesis/stateful/state_machine.py,sha256=PFztY82W5enuXjO6k4Mz8fbHmDJ7Z8OLYZRWtuBeyjg,12956
|
|
146
146
|
schemathesis/stateful/statistic.py,sha256=2-uU5xpT9CbMulKgJWLZN6MUpC0Fskf5yXTt4ef4NFA,542
|
|
147
|
-
schemathesis/stateful/validation.py,sha256=
|
|
148
|
-
schemathesis/transports/__init__.py,sha256=
|
|
147
|
+
schemathesis/stateful/validation.py,sha256=23qSZjC1_xRmtCX4OqsyG6pGxdlo6IZYid695ZpDQyU,3747
|
|
148
|
+
schemathesis/transports/__init__.py,sha256=ybH90TrGwODO5s94UMEX2P2HX-6Jb66X5UUOgKTbZz8,12882
|
|
149
149
|
schemathesis/transports/asgi.py,sha256=bwW9vMd1h89Jh7I4jHJVwSNUQzHvc7-JOD5u4hSHZd8,212
|
|
150
150
|
schemathesis/transports/auth.py,sha256=urSTO9zgFO1qU69xvnKHPFQV0SlJL3d7_Ojl0tLnZwo,1143
|
|
151
151
|
schemathesis/transports/content_types.py,sha256=MiKOm-Hy5i75hrROPdpiBZPOTDzOwlCdnthJD12AJzI,2187
|
|
152
152
|
schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
|
|
153
153
|
schemathesis/transports/responses.py,sha256=OFD4ZLqwEFpo7F9vaP_SVgjhxAqatxIj38FS4XVq8Qs,1680
|
|
154
|
-
schemathesis-3.36.
|
|
155
|
-
schemathesis-3.36.
|
|
156
|
-
schemathesis-3.36.
|
|
157
|
-
schemathesis-3.36.
|
|
158
|
-
schemathesis-3.36.
|
|
154
|
+
schemathesis-3.36.4.dist-info/METADATA,sha256=_FwLfobUt1E2LW6L-xhL-bMbe3VGSOgoykQT8DhOan0,12904
|
|
155
|
+
schemathesis-3.36.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
156
|
+
schemathesis-3.36.4.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
|
|
157
|
+
schemathesis-3.36.4.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
|
|
158
|
+
schemathesis-3.36.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|