schemathesis 4.0.0a10__py3-none-any.whl → 4.0.0a12__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/__init__.py +29 -30
- schemathesis/auths.py +65 -24
- schemathesis/checks.py +73 -39
- schemathesis/cli/commands/__init__.py +51 -3
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +163 -274
- schemathesis/cli/commands/run/context.py +8 -4
- schemathesis/cli/commands/run/events.py +11 -1
- schemathesis/cli/commands/run/executor.py +70 -78
- schemathesis/cli/commands/run/filters.py +15 -165
- schemathesis/cli/commands/run/handlers/cassettes.py +105 -104
- schemathesis/cli/commands/run/handlers/junitxml.py +5 -4
- schemathesis/cli/commands/run/handlers/output.py +195 -121
- schemathesis/cli/commands/run/loaders.py +35 -50
- schemathesis/cli/commands/run/validation.py +52 -162
- schemathesis/cli/core.py +5 -3
- schemathesis/cli/ext/fs.py +7 -5
- schemathesis/cli/ext/options.py +0 -21
- schemathesis/config/__init__.py +189 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +99 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +156 -0
- schemathesis/config/_generation.py +149 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +327 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +187 -0
- schemathesis/config/_projects.py +523 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +25 -0
- schemathesis/config/schema.json +885 -0
- schemathesis/core/__init__.py +2 -0
- schemathesis/core/compat.py +16 -9
- schemathesis/core/errors.py +24 -4
- schemathesis/core/failures.py +6 -7
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/output/__init__.py +14 -37
- schemathesis/core/output/sanitization.py +3 -146
- schemathesis/core/transport.py +36 -1
- schemathesis/core/validation.py +16 -0
- schemathesis/engine/__init__.py +2 -4
- schemathesis/engine/context.py +42 -43
- schemathesis/engine/core.py +7 -5
- schemathesis/engine/errors.py +60 -1
- schemathesis/engine/events.py +10 -2
- schemathesis/engine/phases/__init__.py +10 -0
- schemathesis/engine/phases/probes.py +11 -8
- schemathesis/engine/phases/stateful/__init__.py +2 -1
- schemathesis/engine/phases/stateful/_executor.py +104 -46
- schemathesis/engine/phases/stateful/context.py +2 -2
- schemathesis/engine/phases/unit/__init__.py +23 -15
- schemathesis/engine/phases/unit/_executor.py +110 -21
- schemathesis/engine/phases/unit/_pool.py +1 -1
- schemathesis/errors.py +2 -0
- schemathesis/filters.py +2 -3
- schemathesis/generation/__init__.py +5 -33
- schemathesis/generation/case.py +6 -3
- schemathesis/generation/coverage.py +154 -124
- schemathesis/generation/hypothesis/builder.py +70 -20
- schemathesis/generation/meta.py +3 -3
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +0 -8
- schemathesis/generation/overrides.py +37 -1
- schemathesis/generation/stateful/__init__.py +4 -0
- schemathesis/generation/stateful/state_machine.py +9 -1
- schemathesis/graphql/loaders.py +159 -16
- schemathesis/hooks.py +62 -35
- schemathesis/openapi/checks.py +12 -8
- schemathesis/openapi/generation/filters.py +10 -8
- schemathesis/openapi/loaders.py +142 -17
- schemathesis/pytest/lazy.py +2 -5
- schemathesis/pytest/loaders.py +24 -0
- schemathesis/pytest/plugin.py +33 -2
- schemathesis/schemas.py +21 -66
- schemathesis/specs/graphql/scalars.py +37 -3
- schemathesis/specs/graphql/schemas.py +23 -18
- schemathesis/specs/openapi/_hypothesis.py +26 -28
- schemathesis/specs/openapi/checks.py +37 -36
- schemathesis/specs/openapi/examples.py +4 -3
- schemathesis/specs/openapi/formats.py +32 -5
- schemathesis/specs/openapi/media_types.py +44 -1
- schemathesis/specs/openapi/negative/__init__.py +2 -2
- schemathesis/specs/openapi/patterns.py +46 -16
- schemathesis/specs/openapi/references.py +2 -3
- schemathesis/specs/openapi/schemas.py +19 -22
- schemathesis/specs/openapi/stateful/__init__.py +12 -6
- schemathesis/transport/__init__.py +54 -16
- schemathesis/transport/prepare.py +38 -13
- schemathesis/transport/requests.py +12 -9
- schemathesis/transport/wsgi.py +11 -12
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/METADATA +50 -97
- schemathesis-4.0.0a12.dist-info/RECORD +164 -0
- schemathesis/cli/commands/run/checks.py +0 -79
- schemathesis/cli/commands/run/hypothesis.py +0 -78
- schemathesis/cli/commands/run/reports.py +0 -72
- schemathesis/cli/hooks.py +0 -36
- schemathesis/contrib/__init__.py +0 -9
- schemathesis/contrib/openapi/__init__.py +0 -9
- schemathesis/contrib/openapi/fill_missing_examples.py +0 -20
- schemathesis/engine/config.py +0 -59
- schemathesis/experimental/__init__.py +0 -72
- schemathesis/generation/targets.py +0 -69
- schemathesis-4.0.0a10.dist-info/RECORD +0 -153
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/WHEEL +0 -0
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/licenses/LICENSE +0 -0
schemathesis/engine/context.py
CHANGED
@@ -2,14 +2,12 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import time
|
4
4
|
from dataclasses import dataclass
|
5
|
-
from functools import cached_property
|
6
5
|
from typing import TYPE_CHECKING, Any
|
7
6
|
|
8
|
-
from schemathesis.
|
7
|
+
from schemathesis.config import ProjectConfig
|
9
8
|
from schemathesis.core import NOT_SET, NotSet
|
10
|
-
from schemathesis.engine.recorder import ScenarioRecorder
|
11
9
|
from schemathesis.generation.case import Case
|
12
|
-
from schemathesis.schemas import BaseSchema
|
10
|
+
from schemathesis.schemas import APIOperation, BaseSchema
|
13
11
|
|
14
12
|
from .control import ExecutionControl
|
15
13
|
|
@@ -18,8 +16,6 @@ if TYPE_CHECKING:
|
|
18
16
|
|
19
17
|
import requests
|
20
18
|
|
21
|
-
from schemathesis.engine.config import EngineConfig
|
22
|
-
|
23
19
|
|
24
20
|
@dataclass
|
25
21
|
class EngineContext:
|
@@ -28,26 +24,30 @@ class EngineContext:
|
|
28
24
|
schema: BaseSchema
|
29
25
|
control: ExecutionControl
|
30
26
|
outcome_cache: dict[int, BaseException | None]
|
31
|
-
config: EngineConfig
|
32
27
|
start_time: float
|
33
28
|
|
29
|
+
__slots__ = ("schema", "control", "outcome_cache", "start_time", "_session", "_transport_kwargs_cache")
|
30
|
+
|
34
31
|
def __init__(
|
35
32
|
self,
|
36
33
|
*,
|
37
34
|
schema: BaseSchema,
|
38
35
|
stop_event: threading.Event,
|
39
|
-
config: EngineConfig,
|
40
36
|
session: requests.Session | None = None,
|
41
37
|
) -> None:
|
42
38
|
self.schema = schema
|
43
|
-
self.control = ExecutionControl(stop_event=stop_event, max_failures=config.
|
39
|
+
self.control = ExecutionControl(stop_event=stop_event, max_failures=schema.config.max_failures)
|
44
40
|
self.outcome_cache = {}
|
45
|
-
self.config = config
|
46
41
|
self.start_time = time.monotonic()
|
47
42
|
self._session = session
|
43
|
+
self._transport_kwargs_cache: dict[str | None, dict[str, Any]] = {}
|
48
44
|
|
49
45
|
def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
|
50
46
|
|
47
|
+
@property
|
48
|
+
def config(self) -> ProjectConfig:
|
49
|
+
return self.schema.config
|
50
|
+
|
51
51
|
@property
|
52
52
|
def running_time(self) -> float:
|
53
53
|
return time.monotonic() - self.start_time
|
@@ -74,46 +74,45 @@ class EngineContext:
|
|
74
74
|
def get_cached_outcome(self, case: Case) -> BaseException | None | NotSet:
|
75
75
|
return self.outcome_cache.get(hash(case), NOT_SET)
|
76
76
|
|
77
|
-
|
78
|
-
def session(self) -> requests.Session:
|
77
|
+
def get_session(self, *, operation: APIOperation | None = None) -> requests.Session:
|
79
78
|
if self._session is not None:
|
80
79
|
return self._session
|
81
80
|
import requests
|
82
81
|
|
83
82
|
session = requests.Session()
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
if
|
93
|
-
session.
|
83
|
+
session.headers = {}
|
84
|
+
config = self.config
|
85
|
+
|
86
|
+
session.verify = config.tls_verify_for(operation=operation)
|
87
|
+
auth = config.auth_for(operation=operation)
|
88
|
+
if auth is not None:
|
89
|
+
session.auth = auth
|
90
|
+
headers = config.headers_for(operation=operation)
|
91
|
+
if headers:
|
92
|
+
session.headers.update(headers)
|
93
|
+
request_cert = config.request_cert_for(operation=operation)
|
94
|
+
if request_cert is not None:
|
95
|
+
session.cert = request_cert
|
96
|
+
proxy = config.proxy_for(operation=operation)
|
97
|
+
if proxy is not None:
|
98
|
+
session.proxies["all"] = proxy
|
94
99
|
return session
|
95
100
|
|
96
|
-
|
97
|
-
|
101
|
+
def get_transport_kwargs(self, operation: APIOperation | None = None) -> dict[str, Any]:
|
102
|
+
key = operation.label if operation is not None else None
|
103
|
+
cached = self._transport_kwargs_cache.get(key)
|
104
|
+
if cached is not None:
|
105
|
+
return cached.copy()
|
106
|
+
config = self.config
|
98
107
|
kwargs: dict[str, Any] = {
|
99
|
-
"session": self.
|
100
|
-
"headers":
|
101
|
-
"timeout":
|
102
|
-
"verify":
|
103
|
-
"cert":
|
108
|
+
"session": self.get_session(operation=operation),
|
109
|
+
"headers": config.headers_for(operation=operation),
|
110
|
+
"timeout": config.request_timeout_for(operation=operation),
|
111
|
+
"verify": config.tls_verify_for(operation=operation),
|
112
|
+
"cert": config.request_cert_for(operation=operation),
|
104
113
|
}
|
105
|
-
|
106
|
-
|
114
|
+
proxy = config.proxy_for(operation=operation)
|
115
|
+
if proxy is not None:
|
116
|
+
kwargs["proxies"] = {"all": proxy}
|
117
|
+
self._transport_kwargs_cache[key] = kwargs
|
107
118
|
return kwargs
|
108
|
-
|
109
|
-
def get_check_context(self, recorder: ScenarioRecorder) -> CheckContext:
|
110
|
-
from requests.models import CaseInsensitiveDict
|
111
|
-
|
112
|
-
return CheckContext(
|
113
|
-
override=self.config.override,
|
114
|
-
auth=self.config.network.auth,
|
115
|
-
headers=CaseInsensitiveDict(self.config.network.headers) if self.config.network.headers else None,
|
116
|
-
config=self.config.checks_config,
|
117
|
-
transport_kwargs=self.transport_kwargs,
|
118
|
-
recorder=recorder,
|
119
|
-
)
|
schemathesis/engine/core.py
CHANGED
@@ -9,7 +9,6 @@ from schemathesis.core import SpecificationFeature
|
|
9
9
|
from schemathesis.engine import Status, events, phases
|
10
10
|
from schemathesis.schemas import BaseSchema
|
11
11
|
|
12
|
-
from .config import EngineConfig
|
13
12
|
from .context import EngineContext
|
14
13
|
from .events import EventGenerator
|
15
14
|
from .phases import Phase, PhaseName, PhaseSkipReason
|
@@ -18,15 +17,14 @@ from .phases import Phase, PhaseName, PhaseSkipReason
|
|
18
17
|
@dataclass
|
19
18
|
class Engine:
|
20
19
|
schema: BaseSchema
|
21
|
-
config: EngineConfig
|
22
20
|
|
23
21
|
def execute(self) -> EventStream:
|
24
22
|
"""Execute all test phases."""
|
25
23
|
# Unregister auth if explicitly provided
|
26
|
-
if self.config.
|
24
|
+
if self.schema.config.auth.is_defined:
|
27
25
|
unregister_auth()
|
28
26
|
|
29
|
-
ctx = EngineContext(schema=self.schema, stop_event=threading.Event()
|
27
|
+
ctx = EngineContext(schema=self.schema, stop_event=threading.Event())
|
30
28
|
plan = self._create_execution_plan()
|
31
29
|
return EventStream(plan.execute(ctx), ctx.control.stop_event)
|
32
30
|
|
@@ -70,7 +68,11 @@ class Engine:
|
|
70
68
|
skip_reason=PhaseSkipReason.NOT_SUPPORTED,
|
71
69
|
)
|
72
70
|
|
73
|
-
|
71
|
+
phase = phase_name.value.lower()
|
72
|
+
if (
|
73
|
+
phase in ("examples", "coverage", "fuzzing", "stateful")
|
74
|
+
and not self.schema.config.phases.get_by_name(name=phase).enabled
|
75
|
+
):
|
74
76
|
return Phase(
|
75
77
|
name=phase_name,
|
76
78
|
is_supported=True,
|
schemathesis/engine/errors.py
CHANGED
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
8
8
|
|
9
9
|
import enum
|
10
10
|
import re
|
11
|
+
from dataclasses import dataclass
|
11
12
|
from functools import cached_property
|
12
13
|
from typing import TYPE_CHECKING, Callable, Iterator, Sequence, cast
|
13
14
|
|
@@ -24,6 +25,8 @@ from schemathesis.core.errors import (
|
|
24
25
|
|
25
26
|
if TYPE_CHECKING:
|
26
27
|
import hypothesis.errors
|
28
|
+
import requests
|
29
|
+
from requests.exceptions import ChunkedEncodingError
|
27
30
|
|
28
31
|
__all__ = ["EngineErrorInfo", "DeadlineExceeded", "UnsupportedRecursiveReference", "UnexpectedError"]
|
29
32
|
|
@@ -61,8 +64,9 @@ class EngineErrorInfo:
|
|
61
64
|
It serves as a caching wrapper around exceptions to avoid repeated computations.
|
62
65
|
"""
|
63
66
|
|
64
|
-
def __init__(self, error: Exception) -> None:
|
67
|
+
def __init__(self, error: Exception, code_sample: str | None = None) -> None:
|
65
68
|
self._error = error
|
69
|
+
self._code_sample = code_sample
|
66
70
|
|
67
71
|
def __str__(self) -> str:
|
68
72
|
return self._error_repr
|
@@ -212,6 +216,9 @@ class EngineErrorInfo:
|
|
212
216
|
message.append("") # Empty line before extras
|
213
217
|
message.extend(f"{indent}{extra}" for extra in extras)
|
214
218
|
|
219
|
+
if self._code_sample is not None:
|
220
|
+
message.append(f"\nReproduce with: \n\n {self._code_sample}")
|
221
|
+
|
215
222
|
# Suggestion
|
216
223
|
suggestion = get_runtime_error_suggestion(self._kind, bold=bold)
|
217
224
|
if suggestion is not None:
|
@@ -403,3 +410,55 @@ def canonicalize_error_message(error: Exception, with_traceback: bool = True) ->
|
|
403
410
|
message = MEMORY_ADDRESS_RE.sub("0xbaaaaaaaaaad", message)
|
404
411
|
# Remove URL information
|
405
412
|
return URL_IN_ERROR_MESSAGE_RE.sub("", message)
|
413
|
+
|
414
|
+
|
415
|
+
def clear_hypothesis_notes(exc: Exception) -> None:
|
416
|
+
notes = getattr(exc, "__notes__", [])
|
417
|
+
if any("while generating" in note for note in notes):
|
418
|
+
notes.clear()
|
419
|
+
|
420
|
+
|
421
|
+
def is_unrecoverable_network_error(exc: Exception) -> bool:
|
422
|
+
from http.client import RemoteDisconnected
|
423
|
+
|
424
|
+
from urllib3.exceptions import ProtocolError
|
425
|
+
|
426
|
+
def has_connection_reset(inner: BaseException) -> bool:
|
427
|
+
exc_str = str(inner)
|
428
|
+
if any(pattern in exc_str for pattern in ["Connection reset by peer", "[Errno 104]", "ECONNRESET"]):
|
429
|
+
return True
|
430
|
+
|
431
|
+
if inner.__context__ is not None:
|
432
|
+
return has_connection_reset(inner.__context__)
|
433
|
+
|
434
|
+
return False
|
435
|
+
|
436
|
+
if isinstance(exc.__context__, ProtocolError):
|
437
|
+
if len(exc.__context__.args) == 2 and isinstance(exc.__context__.args[1], RemoteDisconnected):
|
438
|
+
return True
|
439
|
+
if len(exc.__context__.args) == 1 and exc.__context__.args[0] == "Response ended prematurely":
|
440
|
+
return True
|
441
|
+
|
442
|
+
return has_connection_reset(exc)
|
443
|
+
|
444
|
+
|
445
|
+
@dataclass()
|
446
|
+
class UnrecoverableNetworkError:
|
447
|
+
error: requests.ConnectionError | ChunkedEncodingError
|
448
|
+
code_sample: str
|
449
|
+
|
450
|
+
__slots__ = ("error", "code_sample")
|
451
|
+
|
452
|
+
def __init__(self, error: requests.ConnectionError | ChunkedEncodingError, code_sample: str) -> None:
|
453
|
+
self.error = error
|
454
|
+
self.code_sample = code_sample
|
455
|
+
|
456
|
+
|
457
|
+
@dataclass
|
458
|
+
class TestingState:
|
459
|
+
unrecoverable_network_error: UnrecoverableNetworkError | None
|
460
|
+
|
461
|
+
__slots__ = ("unrecoverable_network_error",)
|
462
|
+
|
463
|
+
def __init__(self) -> None:
|
464
|
+
self.unrecoverable_network_error = None
|
schemathesis/engine/events.py
CHANGED
@@ -200,10 +200,18 @@ class NonFatalError(EngineEvent):
|
|
200
200
|
|
201
201
|
__slots__ = ("id", "timestamp", "info", "value", "phase", "label", "related_to_operation")
|
202
202
|
|
203
|
-
def __init__(
|
203
|
+
def __init__(
|
204
|
+
self,
|
205
|
+
*,
|
206
|
+
error: Exception,
|
207
|
+
phase: PhaseName,
|
208
|
+
label: str,
|
209
|
+
related_to_operation: bool,
|
210
|
+
code_sample: str | None = None,
|
211
|
+
) -> None:
|
204
212
|
self.id = uuid.uuid4()
|
205
213
|
self.timestamp = time.time()
|
206
|
-
self.info = EngineErrorInfo(error=error)
|
214
|
+
self.info = EngineErrorInfo(error=error, code_sample=code_sample)
|
207
215
|
self.value = error
|
208
216
|
self.phase = phase
|
209
217
|
self.label = label
|
@@ -23,6 +23,16 @@ class PhaseName(str, enum.Enum):
|
|
23
23
|
def defaults(cls) -> list[PhaseName]:
|
24
24
|
return [PhaseName.EXAMPLES, PhaseName.COVERAGE, PhaseName.FUZZING, PhaseName.STATEFUL_TESTING]
|
25
25
|
|
26
|
+
@property
|
27
|
+
def name(self) -> str:
|
28
|
+
return {
|
29
|
+
PhaseName.PROBING: "probing",
|
30
|
+
PhaseName.EXAMPLES: "examples",
|
31
|
+
PhaseName.COVERAGE: "coverage",
|
32
|
+
PhaseName.FUZZING: "fuzzing",
|
33
|
+
PhaseName.STATEFUL_TESTING: "stateful",
|
34
|
+
}[self]
|
35
|
+
|
26
36
|
@classmethod
|
27
37
|
def from_str(cls, value: str) -> PhaseName:
|
28
38
|
return {
|
@@ -16,11 +16,11 @@ from typing import TYPE_CHECKING
|
|
16
16
|
from schemathesis.core.result import Err, Ok, Result
|
17
17
|
from schemathesis.core.transport import USER_AGENT
|
18
18
|
from schemathesis.engine import Status, events
|
19
|
+
from schemathesis.transport.prepare import get_default_headers
|
19
20
|
|
20
21
|
if TYPE_CHECKING:
|
21
22
|
import requests
|
22
23
|
|
23
|
-
from schemathesis.engine.config import NetworkConfig
|
24
24
|
from schemathesis.engine.context import EngineContext
|
25
25
|
from schemathesis.engine.events import EventGenerator
|
26
26
|
from schemathesis.engine.phases import Phase
|
@@ -36,7 +36,7 @@ class ProbePayload:
|
|
36
36
|
|
37
37
|
def execute(ctx: EngineContext, phase: Phase) -> EventGenerator:
|
38
38
|
"""Discover capabilities of the tested app."""
|
39
|
-
probes = run(ctx
|
39
|
+
probes = run(ctx)
|
40
40
|
status = Status.SUCCESS
|
41
41
|
payload: Result[ProbePayload, Exception] | None = None
|
42
42
|
for result in probes:
|
@@ -44,7 +44,7 @@ def execute(ctx: EngineContext, phase: Phase) -> EventGenerator:
|
|
44
44
|
from ...specs.openapi import formats
|
45
45
|
from ...specs.openapi.formats import HEADER_FORMAT, header_values
|
46
46
|
|
47
|
-
formats.register(HEADER_FORMAT, header_values(
|
47
|
+
formats.register(HEADER_FORMAT, header_values(exclude_characters="\n\r\x00"))
|
48
48
|
if result.error is not None:
|
49
49
|
status = Status.ERROR
|
50
50
|
payload = Err(result.error)
|
@@ -54,9 +54,9 @@ def execute(ctx: EngineContext, phase: Phase) -> EventGenerator:
|
|
54
54
|
yield events.PhaseFinished(phase=phase, status=status, payload=payload)
|
55
55
|
|
56
56
|
|
57
|
-
def run(
|
57
|
+
def run(ctx: EngineContext) -> list[ProbeRun]:
|
58
58
|
"""Run all probes against the given schema."""
|
59
|
-
return [send(probe(),
|
59
|
+
return [send(probe(), ctx) for probe in PROBES]
|
60
60
|
|
61
61
|
|
62
62
|
HEADER_NAME = "X-Schemathesis-Probe"
|
@@ -124,19 +124,22 @@ class NullByteInHeader(Probe):
|
|
124
124
|
PROBES = (NullByteInHeader,)
|
125
125
|
|
126
126
|
|
127
|
-
def send(probe: Probe,
|
127
|
+
def send(probe: Probe, ctx: EngineContext) -> ProbeRun:
|
128
128
|
"""Send the probe to the application."""
|
129
129
|
from requests import PreparedRequest, Request, RequestException
|
130
130
|
from requests.exceptions import MissingSchema
|
131
131
|
from urllib3.exceptions import InsecureRequestWarning
|
132
132
|
|
133
133
|
try:
|
134
|
-
|
134
|
+
session = ctx.get_session()
|
135
|
+
request = probe.prepare_request(session, Request(), ctx.schema)
|
135
136
|
request.headers[HEADER_NAME] = probe.name
|
136
137
|
request.headers["User-Agent"] = USER_AGENT
|
138
|
+
for header, value in get_default_headers().items():
|
139
|
+
request.headers.setdefault(header, value)
|
137
140
|
with warnings.catch_warnings():
|
138
141
|
warnings.simplefilter("ignore", InsecureRequestWarning)
|
139
|
-
response = session.send(request, timeout=config.
|
142
|
+
response = session.send(request, timeout=ctx.config.request_timeout or 2)
|
140
143
|
except MissingSchema:
|
141
144
|
# In-process ASGI/WSGI testing will have local URLs and requires extra handling
|
142
145
|
# which is not currently implemented
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
|
|
6
6
|
|
7
7
|
from schemathesis.engine import Status, events
|
8
8
|
from schemathesis.engine.phases import Phase, PhaseName, PhaseSkipReason
|
9
|
+
from schemathesis.generation.stateful import STATEFUL_TESTS_LABEL
|
9
10
|
|
10
11
|
if TYPE_CHECKING:
|
11
12
|
from schemathesis.engine.context import EngineContext
|
@@ -19,7 +20,7 @@ def execute(engine: EngineContext, phase: Phase) -> events.EventGenerator:
|
|
19
20
|
try:
|
20
21
|
state_machine = engine.schema.as_state_machine()
|
21
22
|
except Exception as exc:
|
22
|
-
yield events.NonFatalError(error=exc, phase=phase.name, label=
|
23
|
+
yield events.NonFatalError(error=exc, phase=phase.name, label=STATEFUL_TESTS_LABEL, related_to_operation=False)
|
23
24
|
yield events.PhaseFinished(phase=phase, status=Status.ERROR, payload=None)
|
24
25
|
return
|
25
26
|
|