schemathesis 3.13.0__py3-none-any.whl → 4.4.2__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.
Files changed (245) hide show
  1. schemathesis/__init__.py +53 -25
  2. schemathesis/auths.py +507 -0
  3. schemathesis/checks.py +190 -25
  4. schemathesis/cli/__init__.py +27 -1016
  5. schemathesis/cli/__main__.py +4 -0
  6. schemathesis/cli/commands/__init__.py +133 -0
  7. schemathesis/cli/commands/data.py +10 -0
  8. schemathesis/cli/commands/run/__init__.py +602 -0
  9. schemathesis/cli/commands/run/context.py +228 -0
  10. schemathesis/cli/commands/run/events.py +60 -0
  11. schemathesis/cli/commands/run/executor.py +157 -0
  12. schemathesis/cli/commands/run/filters.py +53 -0
  13. schemathesis/cli/commands/run/handlers/__init__.py +46 -0
  14. schemathesis/cli/commands/run/handlers/base.py +45 -0
  15. schemathesis/cli/commands/run/handlers/cassettes.py +464 -0
  16. schemathesis/cli/commands/run/handlers/junitxml.py +60 -0
  17. schemathesis/cli/commands/run/handlers/output.py +1750 -0
  18. schemathesis/cli/commands/run/loaders.py +118 -0
  19. schemathesis/cli/commands/run/validation.py +256 -0
  20. schemathesis/cli/constants.py +5 -0
  21. schemathesis/cli/core.py +19 -0
  22. schemathesis/cli/ext/fs.py +16 -0
  23. schemathesis/cli/ext/groups.py +203 -0
  24. schemathesis/cli/ext/options.py +81 -0
  25. schemathesis/config/__init__.py +202 -0
  26. schemathesis/config/_auth.py +51 -0
  27. schemathesis/config/_checks.py +268 -0
  28. schemathesis/config/_diff_base.py +101 -0
  29. schemathesis/config/_env.py +21 -0
  30. schemathesis/config/_error.py +163 -0
  31. schemathesis/config/_generation.py +157 -0
  32. schemathesis/config/_health_check.py +24 -0
  33. schemathesis/config/_operations.py +335 -0
  34. schemathesis/config/_output.py +171 -0
  35. schemathesis/config/_parameters.py +19 -0
  36. schemathesis/config/_phases.py +253 -0
  37. schemathesis/config/_projects.py +543 -0
  38. schemathesis/config/_rate_limit.py +17 -0
  39. schemathesis/config/_report.py +120 -0
  40. schemathesis/config/_validator.py +9 -0
  41. schemathesis/config/_warnings.py +89 -0
  42. schemathesis/config/schema.json +975 -0
  43. schemathesis/core/__init__.py +72 -0
  44. schemathesis/core/adapter.py +34 -0
  45. schemathesis/core/compat.py +32 -0
  46. schemathesis/core/control.py +2 -0
  47. schemathesis/core/curl.py +100 -0
  48. schemathesis/core/deserialization.py +210 -0
  49. schemathesis/core/errors.py +588 -0
  50. schemathesis/core/failures.py +316 -0
  51. schemathesis/core/fs.py +19 -0
  52. schemathesis/core/hooks.py +20 -0
  53. schemathesis/core/jsonschema/__init__.py +13 -0
  54. schemathesis/core/jsonschema/bundler.py +183 -0
  55. schemathesis/core/jsonschema/keywords.py +40 -0
  56. schemathesis/core/jsonschema/references.py +222 -0
  57. schemathesis/core/jsonschema/types.py +41 -0
  58. schemathesis/core/lazy_import.py +15 -0
  59. schemathesis/core/loaders.py +107 -0
  60. schemathesis/core/marks.py +66 -0
  61. schemathesis/core/media_types.py +79 -0
  62. schemathesis/core/output/__init__.py +46 -0
  63. schemathesis/core/output/sanitization.py +54 -0
  64. schemathesis/core/parameters.py +45 -0
  65. schemathesis/core/rate_limit.py +60 -0
  66. schemathesis/core/registries.py +34 -0
  67. schemathesis/core/result.py +27 -0
  68. schemathesis/core/schema_analysis.py +17 -0
  69. schemathesis/core/shell.py +203 -0
  70. schemathesis/core/transforms.py +144 -0
  71. schemathesis/core/transport.py +223 -0
  72. schemathesis/core/validation.py +73 -0
  73. schemathesis/core/version.py +7 -0
  74. schemathesis/engine/__init__.py +28 -0
  75. schemathesis/engine/context.py +152 -0
  76. schemathesis/engine/control.py +44 -0
  77. schemathesis/engine/core.py +201 -0
  78. schemathesis/engine/errors.py +446 -0
  79. schemathesis/engine/events.py +284 -0
  80. schemathesis/engine/observations.py +42 -0
  81. schemathesis/engine/phases/__init__.py +108 -0
  82. schemathesis/engine/phases/analysis.py +28 -0
  83. schemathesis/engine/phases/probes.py +172 -0
  84. schemathesis/engine/phases/stateful/__init__.py +68 -0
  85. schemathesis/engine/phases/stateful/_executor.py +364 -0
  86. schemathesis/engine/phases/stateful/context.py +85 -0
  87. schemathesis/engine/phases/unit/__init__.py +220 -0
  88. schemathesis/engine/phases/unit/_executor.py +459 -0
  89. schemathesis/engine/phases/unit/_pool.py +82 -0
  90. schemathesis/engine/recorder.py +254 -0
  91. schemathesis/errors.py +47 -0
  92. schemathesis/filters.py +395 -0
  93. schemathesis/generation/__init__.py +25 -0
  94. schemathesis/generation/case.py +478 -0
  95. schemathesis/generation/coverage.py +1528 -0
  96. schemathesis/generation/hypothesis/__init__.py +121 -0
  97. schemathesis/generation/hypothesis/builder.py +992 -0
  98. schemathesis/generation/hypothesis/examples.py +56 -0
  99. schemathesis/generation/hypothesis/given.py +66 -0
  100. schemathesis/generation/hypothesis/reporting.py +285 -0
  101. schemathesis/generation/meta.py +227 -0
  102. schemathesis/generation/metrics.py +93 -0
  103. schemathesis/generation/modes.py +20 -0
  104. schemathesis/generation/overrides.py +127 -0
  105. schemathesis/generation/stateful/__init__.py +37 -0
  106. schemathesis/generation/stateful/state_machine.py +294 -0
  107. schemathesis/graphql/__init__.py +15 -0
  108. schemathesis/graphql/checks.py +109 -0
  109. schemathesis/graphql/loaders.py +285 -0
  110. schemathesis/hooks.py +270 -91
  111. schemathesis/openapi/__init__.py +13 -0
  112. schemathesis/openapi/checks.py +467 -0
  113. schemathesis/openapi/generation/__init__.py +0 -0
  114. schemathesis/openapi/generation/filters.py +72 -0
  115. schemathesis/openapi/loaders.py +315 -0
  116. schemathesis/pytest/__init__.py +5 -0
  117. schemathesis/pytest/control_flow.py +7 -0
  118. schemathesis/pytest/lazy.py +341 -0
  119. schemathesis/pytest/loaders.py +36 -0
  120. schemathesis/pytest/plugin.py +357 -0
  121. schemathesis/python/__init__.py +0 -0
  122. schemathesis/python/asgi.py +12 -0
  123. schemathesis/python/wsgi.py +12 -0
  124. schemathesis/schemas.py +683 -247
  125. schemathesis/specs/graphql/__init__.py +0 -1
  126. schemathesis/specs/graphql/nodes.py +27 -0
  127. schemathesis/specs/graphql/scalars.py +86 -0
  128. schemathesis/specs/graphql/schemas.py +395 -123
  129. schemathesis/specs/graphql/validation.py +33 -0
  130. schemathesis/specs/openapi/__init__.py +9 -1
  131. schemathesis/specs/openapi/_hypothesis.py +578 -317
  132. schemathesis/specs/openapi/adapter/__init__.py +10 -0
  133. schemathesis/specs/openapi/adapter/parameters.py +729 -0
  134. schemathesis/specs/openapi/adapter/protocol.py +59 -0
  135. schemathesis/specs/openapi/adapter/references.py +19 -0
  136. schemathesis/specs/openapi/adapter/responses.py +368 -0
  137. schemathesis/specs/openapi/adapter/security.py +144 -0
  138. schemathesis/specs/openapi/adapter/v2.py +30 -0
  139. schemathesis/specs/openapi/adapter/v3_0.py +30 -0
  140. schemathesis/specs/openapi/adapter/v3_1.py +30 -0
  141. schemathesis/specs/openapi/analysis.py +96 -0
  142. schemathesis/specs/openapi/checks.py +753 -74
  143. schemathesis/specs/openapi/converter.py +176 -37
  144. schemathesis/specs/openapi/definitions.py +599 -4
  145. schemathesis/specs/openapi/examples.py +581 -165
  146. schemathesis/specs/openapi/expressions/__init__.py +52 -5
  147. schemathesis/specs/openapi/expressions/extractors.py +25 -0
  148. schemathesis/specs/openapi/expressions/lexer.py +34 -31
  149. schemathesis/specs/openapi/expressions/nodes.py +97 -46
  150. schemathesis/specs/openapi/expressions/parser.py +35 -13
  151. schemathesis/specs/openapi/formats.py +122 -0
  152. schemathesis/specs/openapi/media_types.py +75 -0
  153. schemathesis/specs/openapi/negative/__init__.py +117 -68
  154. schemathesis/specs/openapi/negative/mutations.py +294 -104
  155. schemathesis/specs/openapi/negative/utils.py +3 -6
  156. schemathesis/specs/openapi/patterns.py +458 -0
  157. schemathesis/specs/openapi/references.py +60 -81
  158. schemathesis/specs/openapi/schemas.py +648 -650
  159. schemathesis/specs/openapi/serialization.py +53 -30
  160. schemathesis/specs/openapi/stateful/__init__.py +404 -69
  161. schemathesis/specs/openapi/stateful/control.py +87 -0
  162. schemathesis/specs/openapi/stateful/dependencies/__init__.py +232 -0
  163. schemathesis/specs/openapi/stateful/dependencies/inputs.py +428 -0
  164. schemathesis/specs/openapi/stateful/dependencies/models.py +341 -0
  165. schemathesis/specs/openapi/stateful/dependencies/naming.py +491 -0
  166. schemathesis/specs/openapi/stateful/dependencies/outputs.py +34 -0
  167. schemathesis/specs/openapi/stateful/dependencies/resources.py +339 -0
  168. schemathesis/specs/openapi/stateful/dependencies/schemas.py +447 -0
  169. schemathesis/specs/openapi/stateful/inference.py +254 -0
  170. schemathesis/specs/openapi/stateful/links.py +219 -78
  171. schemathesis/specs/openapi/types/__init__.py +3 -0
  172. schemathesis/specs/openapi/types/common.py +23 -0
  173. schemathesis/specs/openapi/types/v2.py +129 -0
  174. schemathesis/specs/openapi/types/v3.py +134 -0
  175. schemathesis/specs/openapi/utils.py +7 -6
  176. schemathesis/specs/openapi/warnings.py +75 -0
  177. schemathesis/transport/__init__.py +224 -0
  178. schemathesis/transport/asgi.py +26 -0
  179. schemathesis/transport/prepare.py +126 -0
  180. schemathesis/transport/requests.py +278 -0
  181. schemathesis/transport/serialization.py +329 -0
  182. schemathesis/transport/wsgi.py +175 -0
  183. schemathesis-4.4.2.dist-info/METADATA +213 -0
  184. schemathesis-4.4.2.dist-info/RECORD +192 -0
  185. {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
  186. schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
  187. {schemathesis-3.13.0.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
  188. schemathesis/_compat.py +0 -41
  189. schemathesis/_hypothesis.py +0 -115
  190. schemathesis/cli/callbacks.py +0 -188
  191. schemathesis/cli/cassettes.py +0 -253
  192. schemathesis/cli/context.py +0 -36
  193. schemathesis/cli/debug.py +0 -21
  194. schemathesis/cli/handlers.py +0 -11
  195. schemathesis/cli/junitxml.py +0 -41
  196. schemathesis/cli/options.py +0 -51
  197. schemathesis/cli/output/__init__.py +0 -1
  198. schemathesis/cli/output/default.py +0 -508
  199. schemathesis/cli/output/short.py +0 -40
  200. schemathesis/constants.py +0 -79
  201. schemathesis/exceptions.py +0 -207
  202. schemathesis/extra/_aiohttp.py +0 -27
  203. schemathesis/extra/_flask.py +0 -10
  204. schemathesis/extra/_server.py +0 -16
  205. schemathesis/extra/pytest_plugin.py +0 -216
  206. schemathesis/failures.py +0 -131
  207. schemathesis/fixups/__init__.py +0 -29
  208. schemathesis/fixups/fast_api.py +0 -30
  209. schemathesis/lazy.py +0 -227
  210. schemathesis/models.py +0 -1041
  211. schemathesis/parameters.py +0 -88
  212. schemathesis/runner/__init__.py +0 -460
  213. schemathesis/runner/events.py +0 -240
  214. schemathesis/runner/impl/__init__.py +0 -3
  215. schemathesis/runner/impl/core.py +0 -755
  216. schemathesis/runner/impl/solo.py +0 -85
  217. schemathesis/runner/impl/threadpool.py +0 -367
  218. schemathesis/runner/serialization.py +0 -189
  219. schemathesis/serializers.py +0 -233
  220. schemathesis/service/__init__.py +0 -3
  221. schemathesis/service/client.py +0 -46
  222. schemathesis/service/constants.py +0 -12
  223. schemathesis/service/events.py +0 -39
  224. schemathesis/service/handler.py +0 -39
  225. schemathesis/service/models.py +0 -7
  226. schemathesis/service/serialization.py +0 -153
  227. schemathesis/service/worker.py +0 -40
  228. schemathesis/specs/graphql/loaders.py +0 -215
  229. schemathesis/specs/openapi/constants.py +0 -7
  230. schemathesis/specs/openapi/expressions/context.py +0 -12
  231. schemathesis/specs/openapi/expressions/pointers.py +0 -29
  232. schemathesis/specs/openapi/filters.py +0 -44
  233. schemathesis/specs/openapi/links.py +0 -302
  234. schemathesis/specs/openapi/loaders.py +0 -453
  235. schemathesis/specs/openapi/parameters.py +0 -413
  236. schemathesis/specs/openapi/security.py +0 -129
  237. schemathesis/specs/openapi/validation.py +0 -24
  238. schemathesis/stateful.py +0 -349
  239. schemathesis/targets.py +0 -32
  240. schemathesis/types.py +0 -38
  241. schemathesis/utils.py +0 -436
  242. schemathesis-3.13.0.dist-info/METADATA +0 -202
  243. schemathesis-3.13.0.dist-info/RECORD +0 -91
  244. schemathesis-3.13.0.dist-info/entry_points.txt +0 -6
  245. /schemathesis/{extra → cli/ext}/__init__.py +0 -0
@@ -0,0 +1,467 @@
1
+ from __future__ import annotations
2
+
3
+ import textwrap
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from schemathesis.config import OutputConfig
7
+ from schemathesis.core.failures import Failure, Severity
8
+ from schemathesis.core.output import truncate_json
9
+
10
+ if TYPE_CHECKING:
11
+ from jsonschema import ValidationError
12
+
13
+
14
+ class UndefinedStatusCode(Failure):
15
+ """Response has a status code that is not defined in the schema."""
16
+
17
+ __slots__ = (
18
+ "operation",
19
+ "status_code",
20
+ "defined_status_codes",
21
+ "allowed_status_codes",
22
+ "message",
23
+ "title",
24
+ "case_id",
25
+ "severity",
26
+ )
27
+
28
+ def __init__(
29
+ self,
30
+ *,
31
+ operation: str,
32
+ status_code: int,
33
+ defined_status_codes: list[str],
34
+ allowed_status_codes: list[int],
35
+ message: str,
36
+ title: str = "Undocumented HTTP status code",
37
+ case_id: str | None = None,
38
+ ) -> None:
39
+ self.operation = operation
40
+ self.status_code = status_code
41
+ self.defined_status_codes = defined_status_codes
42
+ self.allowed_status_codes = allowed_status_codes
43
+ self.message = message
44
+ self.title = title
45
+ self.case_id = case_id
46
+ self.severity = Severity.MEDIUM
47
+
48
+ @property
49
+ def _unique_key(self) -> str:
50
+ return str(self.status_code)
51
+
52
+
53
+ class MissingHeaders(Failure):
54
+ """Some required headers are missing."""
55
+
56
+ __slots__ = ("operation", "missing_headers", "message", "title", "case_id", "severity")
57
+
58
+ def __init__(
59
+ self,
60
+ *,
61
+ operation: str,
62
+ missing_headers: list[str],
63
+ message: str,
64
+ title: str = "Missing required headers",
65
+ case_id: str | None = None,
66
+ ) -> None:
67
+ self.operation = operation
68
+ self.missing_headers = missing_headers
69
+ self.message = message
70
+ self.title = title
71
+ self.case_id = case_id
72
+ self.severity = Severity.MEDIUM
73
+
74
+
75
+ class JsonSchemaError(Failure):
76
+ """Additional information about JSON Schema validation errors."""
77
+
78
+ __slots__ = (
79
+ "operation",
80
+ "validation_message",
81
+ "schema_path",
82
+ "schema",
83
+ "instance_path",
84
+ "instance",
85
+ "message",
86
+ "title",
87
+ "case_id",
88
+ "severity",
89
+ )
90
+
91
+ def __init__(
92
+ self,
93
+ *,
94
+ operation: str,
95
+ validation_message: str,
96
+ schema_path: list[str | int],
97
+ schema: dict[str, Any] | bool,
98
+ instance_path: list[str | int],
99
+ instance: None | bool | float | str | list | dict[str, Any],
100
+ message: str,
101
+ title: str = "Response violates schema",
102
+ case_id: str | None = None,
103
+ ) -> None:
104
+ self.operation = operation
105
+ self.validation_message = validation_message
106
+ self.schema_path = schema_path
107
+ self.schema = schema
108
+ self.instance_path = instance_path
109
+ self.instance = instance
110
+ self.message = message
111
+ self.title = title
112
+ self.case_id = case_id
113
+ self.severity = Severity.HIGH
114
+
115
+ @property
116
+ def _unique_key(self) -> str:
117
+ return "/".join(map(str, self.schema_path))
118
+
119
+ @classmethod
120
+ def from_exception(
121
+ cls,
122
+ *,
123
+ title: str = "Response violates schema",
124
+ operation: str,
125
+ exc: ValidationError,
126
+ config: OutputConfig | None = None,
127
+ ) -> JsonSchemaError:
128
+ schema_path = list(exc.absolute_schema_path)
129
+
130
+ # Reorder schema to prioritize the failing keyword in the output
131
+ schema_to_display = exc.schema
132
+ if isinstance(schema_to_display, dict) and schema_path:
133
+ failing_keyword = schema_path[-1]
134
+ if isinstance(failing_keyword, str) and failing_keyword in schema_to_display:
135
+ # Create a new dict with the failing keyword first
136
+ schema_to_display = {
137
+ failing_keyword: schema_to_display[failing_keyword],
138
+ **{k: v for k, v in schema_to_display.items() if k != failing_keyword},
139
+ }
140
+ schema = textwrap.indent(
141
+ truncate_json(schema_to_display, config=config or OutputConfig(), max_lines=20), prefix=" "
142
+ )
143
+ value = textwrap.indent(
144
+ truncate_json(exc.instance, config=config or OutputConfig(), max_lines=20), prefix=" "
145
+ )
146
+ schema_path = list(exc.absolute_schema_path)
147
+ if len(schema_path) > 1:
148
+ # Exclude the last segment, which is already in the schema
149
+ schema_title = "Schema at "
150
+ for segment in schema_path[:-1]:
151
+ schema_title += f"/{segment}"
152
+ else:
153
+ schema_title = "Schema"
154
+ message = f"{exc.message}\n\n{schema_title}:\n\n{schema}\n\nValue:\n\n{value}"
155
+ return cls(
156
+ operation=operation,
157
+ title=title,
158
+ message=message,
159
+ validation_message=exc.message,
160
+ schema_path=schema_path,
161
+ schema=exc.schema,
162
+ instance_path=list(exc.absolute_path),
163
+ instance=exc.instance,
164
+ )
165
+
166
+
167
+ class MissingContentType(Failure):
168
+ """Content type header is missing."""
169
+
170
+ __slots__ = ("operation", "media_types", "message", "title", "case_id", "severity")
171
+
172
+ def __init__(
173
+ self,
174
+ *,
175
+ operation: str,
176
+ media_types: list[str],
177
+ message: str,
178
+ title: str = "Missing Content-Type header",
179
+ case_id: str | None = None,
180
+ ) -> None:
181
+ self.operation = operation
182
+ self.media_types = media_types
183
+ self.message = message
184
+ self.title = title
185
+ self.case_id = case_id
186
+ self.severity = Severity.MEDIUM
187
+
188
+ @property
189
+ def _unique_key(self) -> str:
190
+ return ""
191
+
192
+
193
+ class MalformedMediaType(Failure):
194
+ """Media type name is malformed."""
195
+
196
+ __slots__ = ("operation", "actual", "defined", "message", "title", "case_id", "severity")
197
+
198
+ def __init__(
199
+ self,
200
+ *,
201
+ operation: str,
202
+ actual: str,
203
+ defined: str,
204
+ message: str,
205
+ title: str = "Malformed media type",
206
+ case_id: str | None = None,
207
+ ) -> None:
208
+ self.operation = operation
209
+ self.actual = actual
210
+ self.defined = defined
211
+ self.message = message
212
+ self.title = title
213
+ self.case_id = case_id
214
+ self.severity = Severity.MEDIUM
215
+
216
+
217
+ class UndefinedContentType(Failure):
218
+ """Response has Content-Type that is not documented in the schema."""
219
+
220
+ __slots__ = (
221
+ "operation",
222
+ "content_type",
223
+ "defined_content_types",
224
+ "message",
225
+ "title",
226
+ "case_id",
227
+ "severity",
228
+ )
229
+
230
+ def __init__(
231
+ self,
232
+ *,
233
+ operation: str,
234
+ content_type: str,
235
+ defined_content_types: list[str],
236
+ message: str,
237
+ title: str = "Undocumented Content-Type",
238
+ case_id: str | None = None,
239
+ ) -> None:
240
+ self.operation = operation
241
+ self.content_type = content_type
242
+ self.defined_content_types = defined_content_types
243
+ self.message = message
244
+ self.title = title
245
+ self.case_id = case_id
246
+ self.severity = Severity.MEDIUM
247
+
248
+ @property
249
+ def _unique_key(self) -> str:
250
+ return self.content_type
251
+
252
+
253
+ class UseAfterFree(Failure):
254
+ """Resource was used after a successful DELETE operation on it."""
255
+
256
+ __slots__ = ("operation", "message", "free", "usage", "title", "case_id", "severity")
257
+
258
+ def __init__(
259
+ self,
260
+ *,
261
+ operation: str,
262
+ message: str,
263
+ free: str,
264
+ usage: str,
265
+ title: str = "Use after free",
266
+ case_id: str | None = None,
267
+ ) -> None:
268
+ self.operation = operation
269
+ self.message = message
270
+ self.free = free
271
+ self.usage = usage
272
+ self.title = title
273
+ self.case_id = case_id
274
+ self.severity = Severity.CRITICAL
275
+
276
+ @property
277
+ def _unique_key(self) -> str:
278
+ return ""
279
+
280
+
281
+ class EnsureResourceAvailability(Failure):
282
+ """Resource is not available immediately after creation."""
283
+
284
+ __slots__ = ("operation", "message", "created_with", "not_available_with", "title", "case_id", "severity")
285
+
286
+ def __init__(
287
+ self,
288
+ *,
289
+ operation: str,
290
+ message: str,
291
+ created_with: str,
292
+ not_available_with: str,
293
+ title: str = "Resource is not available after creation",
294
+ case_id: str | None = None,
295
+ ) -> None:
296
+ self.operation = operation
297
+ self.message = message
298
+ self.created_with = created_with
299
+ self.not_available_with = not_available_with
300
+ self.title = title
301
+ self.case_id = case_id
302
+ self.severity = Severity.MEDIUM
303
+
304
+ @property
305
+ def _unique_key(self) -> str:
306
+ return ""
307
+
308
+
309
+ class IgnoredAuth(Failure):
310
+ """The API operation does not check the specified authentication."""
311
+
312
+ __slots__ = ("operation", "message", "title", "case_id", "severity")
313
+
314
+ def __init__(
315
+ self,
316
+ *,
317
+ operation: str,
318
+ message: str,
319
+ title: str = "API accepts requests without authentication",
320
+ case_id: str | None = None,
321
+ ) -> None:
322
+ self.operation = operation
323
+ self.message = message
324
+ self.title = title
325
+ self.case_id = case_id
326
+ self.severity = Severity.CRITICAL
327
+
328
+ @property
329
+ def _unique_key(self) -> str:
330
+ return ""
331
+
332
+
333
+ class AcceptedNegativeData(Failure):
334
+ """Response with negative data was accepted."""
335
+
336
+ __slots__ = ("operation", "message", "status_code", "expected_statuses", "title", "case_id", "severity")
337
+
338
+ def __init__(
339
+ self,
340
+ *,
341
+ operation: str,
342
+ message: str,
343
+ status_code: int,
344
+ expected_statuses: list[str],
345
+ title: str = "API accepted schema-violating request",
346
+ case_id: str | None = None,
347
+ ) -> None:
348
+ self.operation = operation
349
+ self.message = message
350
+ self.status_code = status_code
351
+ self.expected_statuses = expected_statuses
352
+ self.title = title
353
+ self.case_id = case_id
354
+ self.severity = Severity.MEDIUM
355
+
356
+ @property
357
+ def _unique_key(self) -> str:
358
+ return str(self.status_code)
359
+
360
+
361
+ class RejectedPositiveData(Failure):
362
+ """Response with positive data was rejected."""
363
+
364
+ __slots__ = ("operation", "message", "status_code", "allowed_statuses", "title", "case_id", "severity")
365
+
366
+ def __init__(
367
+ self,
368
+ *,
369
+ operation: str,
370
+ message: str,
371
+ status_code: int,
372
+ allowed_statuses: list[str],
373
+ title: str = "API rejected schema-compliant request",
374
+ case_id: str | None = None,
375
+ ) -> None:
376
+ self.operation = operation
377
+ self.message = message
378
+ self.status_code = status_code
379
+ self.allowed_statuses = allowed_statuses
380
+ self.title = title
381
+ self.case_id = case_id
382
+ self.severity = Severity.MEDIUM
383
+
384
+ @property
385
+ def _unique_key(self) -> str:
386
+ return str(self.status_code)
387
+
388
+
389
+ class MissingHeaderNotRejected(Failure):
390
+ """API did not reject request without required header."""
391
+
392
+ __slots__ = (
393
+ "operation",
394
+ "header_name",
395
+ "status_code",
396
+ "expected_statuses",
397
+ "message",
398
+ "title",
399
+ "case_id",
400
+ "severity",
401
+ )
402
+
403
+ def __init__(
404
+ self,
405
+ *,
406
+ operation: str,
407
+ header_name: str,
408
+ status_code: int,
409
+ expected_statuses: list[int],
410
+ message: str,
411
+ title: str = "Missing header not rejected",
412
+ case_id: str | None = None,
413
+ ) -> None:
414
+ self.operation = operation
415
+ self.header_name = header_name
416
+ self.status_code = status_code
417
+ self.expected_statuses = expected_statuses
418
+ self.message = message
419
+ self.title = title
420
+ self.case_id = case_id
421
+ self.severity = Severity.MEDIUM
422
+
423
+ @property
424
+ def _unique_key(self) -> str:
425
+ return self.header_name
426
+
427
+
428
+ class UnsupportedMethodResponse(Failure):
429
+ """API response for unsupported HTTP method is incorrect."""
430
+
431
+ __slots__ = (
432
+ "operation",
433
+ "method",
434
+ "status_code",
435
+ "allow_header_present",
436
+ "failure_reason",
437
+ "message",
438
+ "title",
439
+ "case_id",
440
+ "severity",
441
+ )
442
+
443
+ def __init__(
444
+ self,
445
+ *,
446
+ operation: str,
447
+ method: str,
448
+ status_code: int,
449
+ allow_header_present: bool | None = None,
450
+ failure_reason: str, # "wrong_status" or "missing_allow_header"
451
+ message: str,
452
+ title: str = "Unsupported methods",
453
+ case_id: str | None = None,
454
+ ) -> None:
455
+ self.operation = operation
456
+ self.method = method
457
+ self.status_code = status_code
458
+ self.allow_header_present = allow_header_present
459
+ self.failure_reason = failure_reason
460
+ self.message = message
461
+ self.title = title
462
+ self.case_id = case_id
463
+ self.severity = Severity.MEDIUM
464
+
465
+ @property
466
+ def _unique_key(self) -> str:
467
+ return self.failure_reason
File without changes
@@ -0,0 +1,72 @@
1
+ from collections.abc import Mapping
2
+ from typing import Any
3
+
4
+ from schemathesis.core import NOT_SET
5
+ from schemathesis.core.validation import contains_unicode_surrogate_pair, has_invalid_characters, is_latin_1_encodable
6
+
7
+ __all__ = [
8
+ "is_valid_path",
9
+ "is_valid_header",
10
+ "is_valid_urlencoded",
11
+ "is_valid_query",
12
+ ]
13
+
14
+
15
+ def is_valid_path(parameters: dict[str, object]) -> bool:
16
+ """Empty strings ("") are excluded from path by urllib3.
17
+
18
+ A path containing to "/" or "%2F" will lead to ambiguous path resolution in
19
+ many frameworks and libraries, such behaviour have been observed in both
20
+ WSGI and ASGI applications.
21
+
22
+ In this case one variable in the path template will be empty, which will lead to 404 in most of the cases.
23
+ Because of it this case doesn't bring much value and might lead to false positives results of Schemathesis runs.
24
+ """
25
+ return not any(is_invalid_path_parameter(value) for value in parameters.values())
26
+
27
+
28
+ def is_invalid_path_parameter(value: Any) -> bool:
29
+ return (
30
+ value in ("/", "")
31
+ or contains_unicode_surrogate_pair(value)
32
+ or (
33
+ isinstance(value, str)
34
+ and (
35
+ ("/" in value or "}" in value or "{" in value)
36
+ # Avoid situations when the path parameter contains only NULL bytes
37
+ # Many webservers remove such bytes and as the result, the test can target a different API operation
38
+ or (len(value) == value.count("\x00"))
39
+ )
40
+ )
41
+ )
42
+
43
+
44
+ def is_valid_header(headers: dict[str, object]) -> bool:
45
+ for name, value in headers.items():
46
+ if not is_latin_1_encodable(value):
47
+ return False
48
+ if has_invalid_characters(name, value):
49
+ return False
50
+ return True
51
+
52
+
53
+ def is_valid_query(query: dict[str, object]) -> bool:
54
+ for name, value in query.items():
55
+ if contains_unicode_surrogate_pair(name) or contains_unicode_surrogate_pair(value):
56
+ return False
57
+ return True
58
+
59
+
60
+ def is_valid_urlencoded(data: object) -> bool:
61
+ # TODO: write a test that will check if `requests` can send it
62
+ if data is NOT_SET or isinstance(data, Mapping):
63
+ return True
64
+
65
+ if hasattr(data, "__iter__"):
66
+ try:
67
+ for _, _ in data:
68
+ pass
69
+ return True
70
+ except (TypeError, ValueError):
71
+ return False
72
+ return False