schemathesis 4.0.0a9__py3-none-any.whl → 4.0.0a11__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 +3 -7
- schemathesis/checks.py +17 -7
- schemathesis/cli/commands/__init__.py +51 -3
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +147 -260
- schemathesis/cli/commands/run/context.py +2 -3
- schemathesis/cli/commands/run/events.py +4 -0
- schemathesis/cli/commands/run/executor.py +60 -73
- schemathesis/cli/commands/run/filters.py +15 -165
- schemathesis/cli/commands/run/handlers/cassettes.py +105 -104
- schemathesis/cli/commands/run/handlers/junitxml.py +6 -5
- schemathesis/cli/commands/run/handlers/output.py +26 -47
- schemathesis/cli/commands/run/loaders.py +35 -50
- schemathesis/cli/commands/run/validation.py +36 -161
- schemathesis/cli/core.py +5 -3
- schemathesis/cli/ext/fs.py +7 -5
- schemathesis/cli/ext/options.py +0 -21
- schemathesis/config/__init__.py +188 -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 +150 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +313 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +151 -0
- schemathesis/config/_projects.py +495 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +116 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/schema.json +837 -0
- schemathesis/core/__init__.py +3 -0
- schemathesis/core/compat.py +16 -9
- schemathesis/core/errors.py +19 -2
- 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/validation.py +16 -0
- schemathesis/engine/__init__.py +2 -4
- schemathesis/engine/context.py +41 -43
- schemathesis/engine/core.py +7 -5
- schemathesis/engine/phases/__init__.py +10 -0
- schemathesis/engine/phases/probes.py +8 -8
- schemathesis/engine/phases/stateful/_executor.py +68 -43
- schemathesis/engine/phases/unit/__init__.py +23 -15
- schemathesis/engine/phases/unit/_executor.py +77 -17
- schemathesis/engine/phases/unit/_pool.py +1 -1
- schemathesis/errors.py +2 -0
- schemathesis/filters.py +2 -3
- schemathesis/generation/__init__.py +6 -31
- schemathesis/generation/case.py +5 -3
- schemathesis/generation/coverage.py +174 -134
- schemathesis/generation/hypothesis/__init__.py +7 -1
- schemathesis/generation/hypothesis/builder.py +40 -14
- schemathesis/generation/meta.py +3 -3
- schemathesis/generation/overrides.py +37 -1
- schemathesis/generation/stateful/state_machine.py +8 -1
- schemathesis/graphql/loaders.py +21 -12
- schemathesis/openapi/checks.py +12 -8
- schemathesis/openapi/generation/filters.py +10 -8
- schemathesis/openapi/loaders.py +22 -13
- schemathesis/pytest/lazy.py +2 -5
- schemathesis/pytest/plugin.py +11 -2
- schemathesis/schemas.py +13 -61
- schemathesis/specs/graphql/schemas.py +11 -15
- schemathesis/specs/openapi/_hypothesis.py +12 -8
- schemathesis/specs/openapi/checks.py +16 -18
- schemathesis/specs/openapi/examples.py +4 -3
- schemathesis/specs/openapi/formats.py +2 -2
- 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 +11 -20
- schemathesis/specs/openapi/stateful/__init__.py +10 -5
- schemathesis/transport/prepare.py +7 -6
- schemathesis/transport/requests.py +3 -1
- schemathesis/transport/wsgi.py +3 -4
- {schemathesis-4.0.0a9.dist-info → schemathesis-4.0.0a11.dist-info}/METADATA +7 -8
- schemathesis-4.0.0a11.dist-info/RECORD +166 -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/engine/config.py +0 -59
- schemathesis/experimental/__init__.py +0 -72
- schemathesis-4.0.0a9.dist-info/RECORD +0 -153
- {schemathesis-4.0.0a9.dist-info → schemathesis-4.0.0a11.dist-info}/WHEEL +0 -0
- {schemathesis-4.0.0a9.dist-info → schemathesis-4.0.0a11.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.0.0a9.dist-info → schemathesis-4.0.0a11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from schemathesis.config._diff_base import DiffBase
|
7
|
+
|
8
|
+
# Exact keys to sanitize
|
9
|
+
DEFAULT_KEYS_TO_SANITIZE = [
|
10
|
+
"phpsessid",
|
11
|
+
"xsrf-token",
|
12
|
+
"_csrf",
|
13
|
+
"_csrf_token",
|
14
|
+
"_session",
|
15
|
+
"_xsrf",
|
16
|
+
"aiohttp_session",
|
17
|
+
"api_key",
|
18
|
+
"api-key",
|
19
|
+
"apikey",
|
20
|
+
"auth",
|
21
|
+
"authorization",
|
22
|
+
"connect.sid",
|
23
|
+
"cookie",
|
24
|
+
"credentials",
|
25
|
+
"csrf",
|
26
|
+
"csrf_token",
|
27
|
+
"csrf-token",
|
28
|
+
"csrftoken",
|
29
|
+
"ip_address",
|
30
|
+
"mysql_pwd",
|
31
|
+
"passwd",
|
32
|
+
"password",
|
33
|
+
"private_key",
|
34
|
+
"private-key",
|
35
|
+
"privatekey",
|
36
|
+
"remote_addr",
|
37
|
+
"remote-addr",
|
38
|
+
"secret",
|
39
|
+
"session",
|
40
|
+
"sessionid",
|
41
|
+
"set_cookie",
|
42
|
+
"set-cookie",
|
43
|
+
"token",
|
44
|
+
"x_api_key",
|
45
|
+
"x-api-key",
|
46
|
+
"x_csrftoken",
|
47
|
+
"x-csrftoken",
|
48
|
+
"x_forwarded_for",
|
49
|
+
"x-forwarded-for",
|
50
|
+
"x_real_ip",
|
51
|
+
"x-real-ip",
|
52
|
+
]
|
53
|
+
|
54
|
+
# Markers indicating potentially sensitive keys
|
55
|
+
DEFAULT_SENSITIVE_MARKERS = [
|
56
|
+
"token",
|
57
|
+
"key",
|
58
|
+
"secret",
|
59
|
+
"password",
|
60
|
+
"auth",
|
61
|
+
"session",
|
62
|
+
"passwd",
|
63
|
+
"credential",
|
64
|
+
]
|
65
|
+
|
66
|
+
DEFAULT_REPLACEMENT = "[Filtered]"
|
67
|
+
|
68
|
+
|
69
|
+
@dataclass(repr=False)
|
70
|
+
class SanitizationConfig(DiffBase):
|
71
|
+
"""Configuration for sanitizing sensitive data."""
|
72
|
+
|
73
|
+
enabled: bool
|
74
|
+
keys_to_sanitize: list[str]
|
75
|
+
sensitive_markers: list[str]
|
76
|
+
replacement: str
|
77
|
+
|
78
|
+
__slots__ = ("enabled", "keys_to_sanitize", "sensitive_markers", "replacement")
|
79
|
+
|
80
|
+
def __init__(
|
81
|
+
self,
|
82
|
+
*,
|
83
|
+
enabled: bool = True,
|
84
|
+
keys_to_sanitize: list[str] | None = None,
|
85
|
+
sensitive_markers: list[str] | None = None,
|
86
|
+
replacement: str | None = None,
|
87
|
+
) -> None:
|
88
|
+
self.enabled = enabled
|
89
|
+
self.keys_to_sanitize = keys_to_sanitize or DEFAULT_KEYS_TO_SANITIZE
|
90
|
+
self.sensitive_markers = sensitive_markers or DEFAULT_SENSITIVE_MARKERS
|
91
|
+
self.replacement = replacement or DEFAULT_REPLACEMENT
|
92
|
+
|
93
|
+
@classmethod
|
94
|
+
def from_dict(cls, data: dict[str, Any]) -> SanitizationConfig:
|
95
|
+
return cls(
|
96
|
+
enabled=data.get("enabled", True),
|
97
|
+
keys_to_sanitize=[k.lower() for k in data.get("keys-to-sanitize", [])] or DEFAULT_KEYS_TO_SANITIZE,
|
98
|
+
sensitive_markers=[m.lower() for m in data.get("sensitive-markers", [])] or DEFAULT_SENSITIVE_MARKERS,
|
99
|
+
replacement=data.get("replacement", DEFAULT_REPLACEMENT),
|
100
|
+
)
|
101
|
+
|
102
|
+
def update(self, *, enabled: bool | None = None) -> None:
|
103
|
+
if enabled is not None:
|
104
|
+
self.enabled = enabled
|
105
|
+
|
106
|
+
|
107
|
+
MAX_PAYLOAD_SIZE = 512
|
108
|
+
MAX_LINES = 10
|
109
|
+
MAX_WIDTH = 80
|
110
|
+
|
111
|
+
|
112
|
+
@dataclass(repr=False)
|
113
|
+
class TruncationConfig(DiffBase):
|
114
|
+
"""Configuration for truncating large output."""
|
115
|
+
|
116
|
+
enabled: bool
|
117
|
+
max_payload_size: int
|
118
|
+
max_lines: int
|
119
|
+
max_width: int
|
120
|
+
|
121
|
+
__slots__ = ("enabled", "max_payload_size", "max_lines", "max_width")
|
122
|
+
|
123
|
+
def __init__(
|
124
|
+
self,
|
125
|
+
*,
|
126
|
+
enabled: bool = True,
|
127
|
+
max_payload_size: int = MAX_PAYLOAD_SIZE,
|
128
|
+
max_lines: int = MAX_LINES,
|
129
|
+
max_width: int = MAX_WIDTH,
|
130
|
+
) -> None:
|
131
|
+
self.enabled = enabled
|
132
|
+
self.max_payload_size = max_payload_size
|
133
|
+
self.max_lines = max_lines
|
134
|
+
self.max_width = max_width
|
135
|
+
|
136
|
+
@classmethod
|
137
|
+
def from_dict(cls, data: dict[str, Any]) -> TruncationConfig:
|
138
|
+
return cls(
|
139
|
+
enabled=data.get("enabled", True),
|
140
|
+
max_payload_size=data.get("max-payload-size", MAX_PAYLOAD_SIZE),
|
141
|
+
max_lines=data.get("max-lines", MAX_LINES),
|
142
|
+
max_width=data.get("max-width", MAX_WIDTH),
|
143
|
+
)
|
144
|
+
|
145
|
+
def update(self, *, enabled: bool | None = None) -> None:
|
146
|
+
if enabled is not None:
|
147
|
+
self.enabled = enabled
|
148
|
+
|
149
|
+
|
150
|
+
@dataclass(repr=False)
|
151
|
+
class OutputConfig(DiffBase):
|
152
|
+
sanitization: SanitizationConfig
|
153
|
+
truncation: TruncationConfig
|
154
|
+
|
155
|
+
__slots__ = ("sanitization", "truncation")
|
156
|
+
|
157
|
+
def __init__(
|
158
|
+
self,
|
159
|
+
*,
|
160
|
+
sanitization: SanitizationConfig | None = None,
|
161
|
+
truncation: TruncationConfig | None = None,
|
162
|
+
) -> None:
|
163
|
+
self.sanitization = sanitization or SanitizationConfig()
|
164
|
+
self.truncation = truncation or TruncationConfig()
|
165
|
+
|
166
|
+
@classmethod
|
167
|
+
def from_dict(cls, data: dict[str, Any]) -> OutputConfig:
|
168
|
+
return cls(
|
169
|
+
sanitization=SanitizationConfig.from_dict(data.get("sanitization", {})),
|
170
|
+
truncation=TruncationConfig.from_dict(data.get("truncation", {})),
|
171
|
+
)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any, Literal
|
4
|
+
|
5
|
+
from schemathesis.config._env import resolve
|
6
|
+
|
7
|
+
# Define valid parameter locations from OpenAPI
|
8
|
+
ParameterLocation = Literal["path", "query", "header", "cookie", "body"]
|
9
|
+
VALID_LOCATIONS: list[ParameterLocation] = ["path", "query", "header", "cookie", "body"]
|
10
|
+
|
11
|
+
|
12
|
+
def load_parameters(data: dict[str, Any]) -> dict[str, Any]:
|
13
|
+
parameters = {}
|
14
|
+
for key, value in data.get("parameters", {}).items():
|
15
|
+
if isinstance(value, str):
|
16
|
+
parameters[key] = resolve(value)
|
17
|
+
else:
|
18
|
+
parameters[key] = value
|
19
|
+
return parameters
|
@@ -0,0 +1,151 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from schemathesis.config._checks import ChecksConfig
|
7
|
+
from schemathesis.config._diff_base import DiffBase
|
8
|
+
from schemathesis.config._generation import GenerationConfig
|
9
|
+
from schemathesis.core import DEFAULT_STATEFUL_STEP_COUNT
|
10
|
+
|
11
|
+
DEFAULT_UNEXPECTED_METHODS = {"get", "put", "post", "delete", "options", "patch", "trace"}
|
12
|
+
|
13
|
+
|
14
|
+
@dataclass(repr=False)
|
15
|
+
class PhaseConfig(DiffBase):
|
16
|
+
enabled: bool
|
17
|
+
generation: GenerationConfig
|
18
|
+
checks: ChecksConfig
|
19
|
+
|
20
|
+
__slots__ = ("enabled", "generation", "checks")
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
*,
|
25
|
+
enabled: bool = True,
|
26
|
+
generation: GenerationConfig | None = None,
|
27
|
+
checks: ChecksConfig | None = None,
|
28
|
+
) -> None:
|
29
|
+
self.enabled = enabled
|
30
|
+
self.generation = generation or GenerationConfig()
|
31
|
+
self.checks = checks or ChecksConfig()
|
32
|
+
|
33
|
+
@classmethod
|
34
|
+
def from_dict(cls, data: dict[str, Any]) -> PhaseConfig:
|
35
|
+
return cls(
|
36
|
+
enabled=data.get("enabled", True),
|
37
|
+
generation=GenerationConfig.from_dict(data.get("generation", {})),
|
38
|
+
checks=ChecksConfig.from_dict(data.get("checks", {})),
|
39
|
+
)
|
40
|
+
|
41
|
+
|
42
|
+
@dataclass(repr=False)
|
43
|
+
class CoveragePhaseConfig(DiffBase):
|
44
|
+
enabled: bool
|
45
|
+
generation: GenerationConfig
|
46
|
+
checks: ChecksConfig
|
47
|
+
unexpected_methods: set[str]
|
48
|
+
|
49
|
+
__slots__ = ("enabled", "generation", "checks", "unexpected_methods")
|
50
|
+
|
51
|
+
def __init__(
|
52
|
+
self,
|
53
|
+
*,
|
54
|
+
enabled: bool = True,
|
55
|
+
generation: GenerationConfig | None = None,
|
56
|
+
checks: ChecksConfig | None = None,
|
57
|
+
unexpected_methods: set[str] | None = None,
|
58
|
+
) -> None:
|
59
|
+
self.enabled = enabled
|
60
|
+
self.unexpected_methods = unexpected_methods or DEFAULT_UNEXPECTED_METHODS
|
61
|
+
self.generation = generation or GenerationConfig()
|
62
|
+
self.checks = checks or ChecksConfig()
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def from_dict(cls, data: dict[str, Any]) -> CoveragePhaseConfig:
|
66
|
+
return cls(
|
67
|
+
enabled=data.get("enabled", True),
|
68
|
+
unexpected_methods={method.lower() for method in data.get("unexpected-methods", [])}
|
69
|
+
if "unexpected-methods" in data
|
70
|
+
else None,
|
71
|
+
generation=GenerationConfig.from_dict(data.get("generation", {})),
|
72
|
+
checks=ChecksConfig.from_dict(data.get("checks", {})),
|
73
|
+
)
|
74
|
+
|
75
|
+
|
76
|
+
@dataclass(repr=False)
|
77
|
+
class StatefulPhaseConfig(DiffBase):
|
78
|
+
enabled: bool
|
79
|
+
generation: GenerationConfig
|
80
|
+
checks: ChecksConfig
|
81
|
+
max_steps: int
|
82
|
+
|
83
|
+
__slots__ = ("enabled", "generation", "checks", "max_steps")
|
84
|
+
|
85
|
+
def __init__(
|
86
|
+
self,
|
87
|
+
*,
|
88
|
+
enabled: bool = True,
|
89
|
+
generation: GenerationConfig | None = None,
|
90
|
+
checks: ChecksConfig | None = None,
|
91
|
+
max_steps: int | None = None,
|
92
|
+
) -> None:
|
93
|
+
self.enabled = enabled
|
94
|
+
self.max_steps = max_steps or DEFAULT_STATEFUL_STEP_COUNT
|
95
|
+
self.generation = generation or GenerationConfig()
|
96
|
+
self.checks = checks or ChecksConfig()
|
97
|
+
|
98
|
+
@classmethod
|
99
|
+
def from_dict(cls, data: dict[str, Any]) -> StatefulPhaseConfig:
|
100
|
+
return cls(
|
101
|
+
enabled=data.get("enabled", True),
|
102
|
+
max_steps=data.get("max-steps"),
|
103
|
+
generation=GenerationConfig.from_dict(data.get("generation", {})),
|
104
|
+
checks=ChecksConfig.from_dict(data.get("checks", {})),
|
105
|
+
)
|
106
|
+
|
107
|
+
|
108
|
+
@dataclass(repr=False)
|
109
|
+
class PhasesConfig(DiffBase):
|
110
|
+
examples: PhaseConfig
|
111
|
+
coverage: CoveragePhaseConfig
|
112
|
+
fuzzing: PhaseConfig
|
113
|
+
stateful: StatefulPhaseConfig
|
114
|
+
|
115
|
+
__slots__ = ("examples", "coverage", "fuzzing", "stateful")
|
116
|
+
|
117
|
+
def __init__(
|
118
|
+
self,
|
119
|
+
*,
|
120
|
+
examples: PhaseConfig | None = None,
|
121
|
+
coverage: CoveragePhaseConfig | None = None,
|
122
|
+
fuzzing: PhaseConfig | None = None,
|
123
|
+
stateful: StatefulPhaseConfig | None = None,
|
124
|
+
) -> None:
|
125
|
+
self.examples = examples or PhaseConfig()
|
126
|
+
self.coverage = coverage or CoveragePhaseConfig()
|
127
|
+
self.fuzzing = fuzzing or PhaseConfig()
|
128
|
+
self.stateful = stateful or StatefulPhaseConfig()
|
129
|
+
|
130
|
+
def get_by_name(self, *, name: str) -> PhaseConfig | CoveragePhaseConfig | StatefulPhaseConfig:
|
131
|
+
return {
|
132
|
+
"examples": self.examples,
|
133
|
+
"coverage": self.coverage,
|
134
|
+
"fuzzing": self.fuzzing,
|
135
|
+
"stateful": self.stateful,
|
136
|
+
}[name] # type: ignore[return-value]
|
137
|
+
|
138
|
+
@classmethod
|
139
|
+
def from_dict(cls, data: dict[str, Any]) -> PhasesConfig:
|
140
|
+
return cls(
|
141
|
+
examples=PhaseConfig.from_dict(data.get("examples", {})),
|
142
|
+
coverage=CoveragePhaseConfig.from_dict(data.get("coverage", {})),
|
143
|
+
fuzzing=PhaseConfig.from_dict(data.get("fuzzing", {})),
|
144
|
+
stateful=StatefulPhaseConfig.from_dict(data.get("stateful", {})),
|
145
|
+
)
|
146
|
+
|
147
|
+
def update(self, *, phases: list[str]) -> None:
|
148
|
+
self.examples.enabled = "examples" in phases
|
149
|
+
self.coverage.enabled = "coverage" in phases
|
150
|
+
self.fuzzing.enabled = "fuzzing" in phases
|
151
|
+
self.stateful.enabled = "stateful" in phases
|