schemathesis 3.38.9__py3-none-any.whl → 3.39.0__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/_xml.py +69 -20
- schemathesis/cli/__init__.py +13 -0
- schemathesis/internal/checks.py +1 -0
- schemathesis/models.py +6 -1
- schemathesis/runner/__init__.py +7 -0
- schemathesis/runner/impl/context.py +15 -2
- schemathesis/runner/impl/core.py +23 -12
- schemathesis/specs/graphql/schemas.py +4 -1
- schemathesis/specs/openapi/checks.py +16 -2
- {schemathesis-3.38.9.dist-info → schemathesis-3.39.0.dist-info}/METADATA +4 -3
- {schemathesis-3.38.9.dist-info → schemathesis-3.39.0.dist-info}/RECORD +14 -14
- {schemathesis-3.38.9.dist-info → schemathesis-3.39.0.dist-info}/WHEEL +1 -1
- {schemathesis-3.38.9.dist-info → schemathesis-3.39.0.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.38.9.dist-info → schemathesis-3.39.0.dist-info}/licenses/LICENSE +0 -0
schemathesis/_xml.py
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
from io import StringIO
|
|
6
7
|
from typing import Any, Dict, List, Union
|
|
7
|
-
from
|
|
8
|
+
from unicodedata import normalize
|
|
8
9
|
|
|
9
10
|
from .exceptions import UnboundPrefixError
|
|
10
11
|
from .internal.copy import fast_deepcopy
|
|
@@ -32,24 +33,9 @@ def _to_xml(value: Any, raw_schema: dict[str, Any] | None, resolved_schema: dict
|
|
|
32
33
|
namespace_stack: list[str] = []
|
|
33
34
|
_write_xml(buffer, value, tag, resolved_schema, namespace_stack)
|
|
34
35
|
data = buffer.getvalue()
|
|
35
|
-
if not is_valid_xml(data):
|
|
36
|
-
from hypothesis import reject
|
|
37
|
-
|
|
38
|
-
reject()
|
|
39
36
|
return {"data": data.encode("utf8")}
|
|
40
37
|
|
|
41
38
|
|
|
42
|
-
_from_string = ElementTree.fromstring
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def is_valid_xml(data: str) -> bool:
|
|
46
|
-
try:
|
|
47
|
-
_from_string(f"<root xmlns:smp='{NAMESPACE_URL}'>{data}</root>")
|
|
48
|
-
return True
|
|
49
|
-
except ElementTree.ParseError:
|
|
50
|
-
return False
|
|
51
|
-
|
|
52
|
-
|
|
53
39
|
def _get_xml_tag(raw_schema: dict[str, Any] | None, resolved_schema: dict[str, Any] | None) -> str:
|
|
54
40
|
# On the top level we need to detect the proper XML tag, in other cases it is known from object properties
|
|
55
41
|
if (resolved_schema or {}).get("xml", {}).get("name"):
|
|
@@ -98,11 +84,14 @@ def _write_object(
|
|
|
98
84
|
) -> None:
|
|
99
85
|
options = (schema or {}).get("xml", {})
|
|
100
86
|
push_namespace_if_any(stack, options)
|
|
87
|
+
tag = _sanitize_xml_name(tag)
|
|
101
88
|
if "prefix" in options:
|
|
102
89
|
tag = f"{options['prefix']}:{tag}"
|
|
103
90
|
buffer.write(f"<{tag}")
|
|
104
91
|
if "namespace" in options:
|
|
105
92
|
_write_namespace(buffer, options)
|
|
93
|
+
|
|
94
|
+
attribute_namespaces = {}
|
|
106
95
|
attributes = []
|
|
107
96
|
children_buffer = StringIO()
|
|
108
97
|
properties = (schema or {}).get("properties", {})
|
|
@@ -111,16 +100,31 @@ def _write_object(
|
|
|
111
100
|
child_options = property_schema.get("xml", {})
|
|
112
101
|
push_namespace_if_any(stack, child_options)
|
|
113
102
|
child_tag = child_options.get("name", child_name)
|
|
103
|
+
|
|
104
|
+
if child_options.get("attribute", False):
|
|
105
|
+
if child_options.get("prefix") and child_options.get("namespace"):
|
|
106
|
+
_validate_prefix(child_options, stack)
|
|
107
|
+
prefix = child_options["prefix"]
|
|
108
|
+
attr_name = f"{prefix}:{_sanitize_xml_name(child_tag)}"
|
|
109
|
+
# Store namespace declaration
|
|
110
|
+
attribute_namespaces[prefix] = child_options["namespace"]
|
|
111
|
+
else:
|
|
112
|
+
attr_name = _sanitize_xml_name(child_tag)
|
|
113
|
+
attributes.append(f'{attr_name}="{_escape_xml(value)}"')
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
child_tag = _sanitize_xml_name(child_tag)
|
|
114
117
|
if child_options.get("prefix"):
|
|
115
118
|
_validate_prefix(child_options, stack)
|
|
116
119
|
prefix = child_options["prefix"]
|
|
117
120
|
child_tag = f"{prefix}:{child_tag}"
|
|
118
|
-
if child_options.get("attribute", False):
|
|
119
|
-
attributes.append(f'{child_tag}="{value}"')
|
|
120
|
-
continue
|
|
121
121
|
_write_xml(children_buffer, value, child_tag, property_schema, stack)
|
|
122
122
|
pop_namespace_if_any(stack, child_options)
|
|
123
123
|
|
|
124
|
+
# Write namespace declarations for attributes
|
|
125
|
+
for prefix, namespace in attribute_namespaces.items():
|
|
126
|
+
buffer.write(f' xmlns:{prefix}="{namespace}"')
|
|
127
|
+
|
|
124
128
|
if attributes:
|
|
125
129
|
buffer.write(f" {' '.join(attributes)}")
|
|
126
130
|
buffer.write(">")
|
|
@@ -169,7 +173,7 @@ def _write_primitive(
|
|
|
169
173
|
buffer.write(f"<{tag}")
|
|
170
174
|
if "namespace" in xml_options:
|
|
171
175
|
_write_namespace(buffer, xml_options)
|
|
172
|
-
buffer.write(f">{obj}</{tag}>")
|
|
176
|
+
buffer.write(f">{_escape_xml(obj)}</{tag}>")
|
|
173
177
|
|
|
174
178
|
|
|
175
179
|
def _write_namespace(buffer: StringIO, options: dict[str, Any]) -> None:
|
|
@@ -182,3 +186,48 @@ def _write_namespace(buffer: StringIO, options: dict[str, Any]) -> None:
|
|
|
182
186
|
def _get_tag_name_from_reference(reference: str) -> str:
|
|
183
187
|
"""Extract object name from a reference."""
|
|
184
188
|
return reference.rsplit("/", maxsplit=1)[1]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _escape_xml(value: JSON) -> str:
|
|
192
|
+
"""Escape special characters in XML content."""
|
|
193
|
+
if isinstance(value, (int, float, bool)):
|
|
194
|
+
return str(value)
|
|
195
|
+
if value is None:
|
|
196
|
+
return ""
|
|
197
|
+
|
|
198
|
+
# Filter out invalid XML characters
|
|
199
|
+
cleaned = "".join(
|
|
200
|
+
char
|
|
201
|
+
for char in str(value)
|
|
202
|
+
if (
|
|
203
|
+
char in "\t\n\r"
|
|
204
|
+
or 0x20 <= ord(char) <= 0xD7FF
|
|
205
|
+
or 0xE000 <= ord(char) <= 0xFFFD
|
|
206
|
+
or 0x10000 <= ord(char) <= 0x10FFFF
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
replacements = {
|
|
211
|
+
"&": "&",
|
|
212
|
+
"<": "<",
|
|
213
|
+
">": ">",
|
|
214
|
+
'"': """,
|
|
215
|
+
"'": "'",
|
|
216
|
+
}
|
|
217
|
+
return "".join(replacements.get(c, c) for c in cleaned)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _sanitize_xml_name(name: str) -> str:
|
|
221
|
+
"""Sanitize a string to be a valid XML element name."""
|
|
222
|
+
if not name:
|
|
223
|
+
return "element"
|
|
224
|
+
|
|
225
|
+
name = normalize("NFKC", str(name))
|
|
226
|
+
|
|
227
|
+
name = name.replace(":", "_")
|
|
228
|
+
sanitized = re.sub(r"[^a-zA-Z0-9_\-.]", "_", name)
|
|
229
|
+
|
|
230
|
+
if not sanitized[0].isalpha() and sanitized[0] != "_":
|
|
231
|
+
sanitized = "x_" + sanitized
|
|
232
|
+
|
|
233
|
+
return sanitized
|
schemathesis/cli/__init__.py
CHANGED
|
@@ -323,6 +323,15 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
323
323
|
multiple=True,
|
|
324
324
|
metavar="",
|
|
325
325
|
)
|
|
326
|
+
@grouped_option(
|
|
327
|
+
"--experimental-no-failfast",
|
|
328
|
+
"no_failfast",
|
|
329
|
+
help="Continue testing an API operation after a failure is found",
|
|
330
|
+
is_flag=True,
|
|
331
|
+
default=False,
|
|
332
|
+
metavar="",
|
|
333
|
+
envvar="SCHEMATHESIS_EXPERIMENTAL_NO_FAILFAST",
|
|
334
|
+
)
|
|
326
335
|
@grouped_option(
|
|
327
336
|
"--experimental-missing-required-header-allowed-statuses",
|
|
328
337
|
"missing_required_header_allowed_statuses",
|
|
@@ -864,6 +873,7 @@ def run(
|
|
|
864
873
|
set_cookie: dict[str, str],
|
|
865
874
|
set_path: dict[str, str],
|
|
866
875
|
experiments: list,
|
|
876
|
+
no_failfast: bool,
|
|
867
877
|
missing_required_header_allowed_statuses: list[str],
|
|
868
878
|
positive_data_acceptance_allowed_statuses: list[str],
|
|
869
879
|
negative_data_rejection_allowed_statuses: list[str],
|
|
@@ -1232,6 +1242,7 @@ def run(
|
|
|
1232
1242
|
request_timeout=request_timeout,
|
|
1233
1243
|
seed=hypothesis_seed,
|
|
1234
1244
|
exit_first=exit_first,
|
|
1245
|
+
no_failfast=no_failfast,
|
|
1235
1246
|
max_failures=max_failures,
|
|
1236
1247
|
unique_data=contrib_unique_data,
|
|
1237
1248
|
dry_run=dry_run,
|
|
@@ -1358,6 +1369,7 @@ def into_event_stream(
|
|
|
1358
1369
|
output_config: OutputConfig,
|
|
1359
1370
|
seed: int | None,
|
|
1360
1371
|
exit_first: bool,
|
|
1372
|
+
no_failfast: bool,
|
|
1361
1373
|
max_failures: int | None,
|
|
1362
1374
|
rate_limit: str | None,
|
|
1363
1375
|
unique_data: bool,
|
|
@@ -1402,6 +1414,7 @@ def into_event_stream(
|
|
|
1402
1414
|
request_cert=request_cert,
|
|
1403
1415
|
seed=seed,
|
|
1404
1416
|
exit_first=exit_first,
|
|
1417
|
+
no_failfast=no_failfast,
|
|
1405
1418
|
max_failures=max_failures,
|
|
1406
1419
|
started_at=started_at,
|
|
1407
1420
|
unique_data=unique_data,
|
schemathesis/internal/checks.py
CHANGED
schemathesis/models.py
CHANGED
|
@@ -474,6 +474,7 @@ class Case:
|
|
|
474
474
|
excluded_checks: tuple[CheckFunction, ...] = (),
|
|
475
475
|
code_sample_style: str | None = None,
|
|
476
476
|
headers: dict[str, Any] | None = None,
|
|
477
|
+
transport_kwargs: dict[str, Any] | None = None,
|
|
477
478
|
) -> None:
|
|
478
479
|
"""Validate application response.
|
|
479
480
|
|
|
@@ -507,7 +508,10 @@ class Case:
|
|
|
507
508
|
additional_checks = tuple(check for check in _additional_checks if check not in excluded_checks)
|
|
508
509
|
failed_checks = []
|
|
509
510
|
ctx = CheckContext(
|
|
510
|
-
override=self._override,
|
|
511
|
+
override=self._override,
|
|
512
|
+
auth=None,
|
|
513
|
+
headers=CaseInsensitiveDict(headers) if headers else None,
|
|
514
|
+
transport_kwargs=transport_kwargs,
|
|
511
515
|
)
|
|
512
516
|
for check in chain(checks, additional_checks):
|
|
513
517
|
copied_case = self.partial_deepcopy()
|
|
@@ -591,6 +595,7 @@ class Case:
|
|
|
591
595
|
headers=headers,
|
|
592
596
|
additional_checks=additional_checks,
|
|
593
597
|
excluded_checks=excluded_checks,
|
|
598
|
+
transport_kwargs=kwargs,
|
|
594
599
|
)
|
|
595
600
|
return response
|
|
596
601
|
|
schemathesis/runner/__init__.py
CHANGED
|
@@ -346,6 +346,7 @@ def from_schema(
|
|
|
346
346
|
request_cert: RequestCert | None = None,
|
|
347
347
|
seed: int | None = None,
|
|
348
348
|
exit_first: bool = False,
|
|
349
|
+
no_failfast: bool = False,
|
|
349
350
|
max_failures: int | None = None,
|
|
350
351
|
started_at: str | None = None,
|
|
351
352
|
unique_data: bool = False,
|
|
@@ -406,6 +407,7 @@ def from_schema(
|
|
|
406
407
|
workers_num=workers_num,
|
|
407
408
|
request_config=request_config,
|
|
408
409
|
exit_first=exit_first,
|
|
410
|
+
no_failfast=no_failfast,
|
|
409
411
|
max_failures=max_failures,
|
|
410
412
|
started_at=started_at,
|
|
411
413
|
unique_data=unique_data,
|
|
@@ -433,6 +435,7 @@ def from_schema(
|
|
|
433
435
|
headers=headers,
|
|
434
436
|
seed=seed,
|
|
435
437
|
exit_first=exit_first,
|
|
438
|
+
no_failfast=no_failfast,
|
|
436
439
|
max_failures=max_failures,
|
|
437
440
|
started_at=started_at,
|
|
438
441
|
unique_data=unique_data,
|
|
@@ -460,6 +463,7 @@ def from_schema(
|
|
|
460
463
|
seed=seed,
|
|
461
464
|
workers_num=workers_num,
|
|
462
465
|
exit_first=exit_first,
|
|
466
|
+
no_failfast=no_failfast,
|
|
463
467
|
max_failures=max_failures,
|
|
464
468
|
started_at=started_at,
|
|
465
469
|
unique_data=unique_data,
|
|
@@ -488,6 +492,7 @@ def from_schema(
|
|
|
488
492
|
seed=seed,
|
|
489
493
|
request_config=request_config,
|
|
490
494
|
exit_first=exit_first,
|
|
495
|
+
no_failfast=no_failfast,
|
|
491
496
|
max_failures=max_failures,
|
|
492
497
|
started_at=started_at,
|
|
493
498
|
unique_data=unique_data,
|
|
@@ -515,6 +520,7 @@ def from_schema(
|
|
|
515
520
|
headers=headers,
|
|
516
521
|
seed=seed,
|
|
517
522
|
exit_first=exit_first,
|
|
523
|
+
no_failfast=no_failfast,
|
|
518
524
|
max_failures=max_failures,
|
|
519
525
|
started_at=started_at,
|
|
520
526
|
unique_data=unique_data,
|
|
@@ -541,6 +547,7 @@ def from_schema(
|
|
|
541
547
|
headers=headers,
|
|
542
548
|
seed=seed,
|
|
543
549
|
exit_first=exit_first,
|
|
550
|
+
no_failfast=no_failfast,
|
|
544
551
|
max_failures=max_failures,
|
|
545
552
|
started_at=started_at,
|
|
546
553
|
unique_data=unique_data,
|
|
@@ -28,8 +28,19 @@ class RunnerContext:
|
|
|
28
28
|
outcome_cache: dict[int, BaseException | None]
|
|
29
29
|
checks_config: CheckConfig
|
|
30
30
|
override: CaseOverride | None
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
no_failfast: bool
|
|
32
|
+
|
|
33
|
+
__slots__ = (
|
|
34
|
+
"data",
|
|
35
|
+
"auth",
|
|
36
|
+
"seed",
|
|
37
|
+
"stop_event",
|
|
38
|
+
"unique_data",
|
|
39
|
+
"outcome_cache",
|
|
40
|
+
"checks_config",
|
|
41
|
+
"override",
|
|
42
|
+
"no_failfast",
|
|
43
|
+
)
|
|
33
44
|
|
|
34
45
|
def __init__(
|
|
35
46
|
self,
|
|
@@ -40,6 +51,7 @@ class RunnerContext:
|
|
|
40
51
|
unique_data: bool,
|
|
41
52
|
checks_config: CheckConfig,
|
|
42
53
|
override: CaseOverride | None,
|
|
54
|
+
no_failfast: bool,
|
|
43
55
|
) -> None:
|
|
44
56
|
self.data = TestResultSet(seed=seed)
|
|
45
57
|
self.auth = auth
|
|
@@ -49,6 +61,7 @@ class RunnerContext:
|
|
|
49
61
|
self.unique_data = unique_data
|
|
50
62
|
self.checks_config = checks_config
|
|
51
63
|
self.override = override
|
|
64
|
+
self.no_failfast = no_failfast
|
|
52
65
|
|
|
53
66
|
def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
|
|
54
67
|
|
schemathesis/runner/impl/core.py
CHANGED
|
@@ -111,6 +111,7 @@ class BaseRunner:
|
|
|
111
111
|
store_interactions: bool = False
|
|
112
112
|
seed: int | None = None
|
|
113
113
|
exit_first: bool = False
|
|
114
|
+
no_failfast: bool = False
|
|
114
115
|
max_failures: int | None = None
|
|
115
116
|
started_at: str = field(default_factory=current_datetime)
|
|
116
117
|
unique_data: bool = False
|
|
@@ -139,6 +140,7 @@ class BaseRunner:
|
|
|
139
140
|
unique_data=self.unique_data,
|
|
140
141
|
checks_config=self.checks_config,
|
|
141
142
|
override=self.override,
|
|
143
|
+
no_failfast=self.no_failfast,
|
|
142
144
|
)
|
|
143
145
|
start_time = time.monotonic()
|
|
144
146
|
initialized = None
|
|
@@ -672,6 +674,8 @@ def run_test(
|
|
|
672
674
|
)
|
|
673
675
|
else:
|
|
674
676
|
result.add_error(error)
|
|
677
|
+
if status == Status.success and ctx.no_failfast and any(check.value == Status.failure for check in result.checks):
|
|
678
|
+
status = Status.failure
|
|
675
679
|
if has_unsatisfied_example_mark(test):
|
|
676
680
|
status = Status.error
|
|
677
681
|
result.add_error(
|
|
@@ -811,6 +815,7 @@ def run_checks(
|
|
|
811
815
|
response: GenericResponse,
|
|
812
816
|
elapsed_time: float,
|
|
813
817
|
max_response_time: int | None = None,
|
|
818
|
+
no_failfast: bool,
|
|
814
819
|
) -> None:
|
|
815
820
|
errors = []
|
|
816
821
|
|
|
@@ -852,7 +857,7 @@ def run_checks(
|
|
|
852
857
|
else:
|
|
853
858
|
result.add_success("max_response_time", case, response, elapsed_time)
|
|
854
859
|
|
|
855
|
-
if errors:
|
|
860
|
+
if errors and not no_failfast:
|
|
856
861
|
raise get_grouped_exception(case.operation.verbose_name, *errors)(causes=tuple(errors))
|
|
857
862
|
|
|
858
863
|
|
|
@@ -994,18 +999,18 @@ def _network_test(
|
|
|
994
999
|
max_response_time: int | None,
|
|
995
1000
|
) -> requests.Response:
|
|
996
1001
|
check_results: list[Check] = []
|
|
1002
|
+
hook_context = HookContext(operation=case.operation)
|
|
1003
|
+
kwargs: dict[str, Any] = {
|
|
1004
|
+
"session": session,
|
|
1005
|
+
"headers": headers,
|
|
1006
|
+
"timeout": request_config.prepared_timeout,
|
|
1007
|
+
"verify": request_config.tls_verify,
|
|
1008
|
+
"cert": request_config.cert,
|
|
1009
|
+
}
|
|
1010
|
+
if request_config.proxy is not None:
|
|
1011
|
+
kwargs["proxies"] = {"all": request_config.proxy}
|
|
1012
|
+
hooks.dispatch("process_call_kwargs", hook_context, case, kwargs)
|
|
997
1013
|
try:
|
|
998
|
-
hook_context = HookContext(operation=case.operation)
|
|
999
|
-
kwargs: dict[str, Any] = {
|
|
1000
|
-
"session": session,
|
|
1001
|
-
"headers": headers,
|
|
1002
|
-
"timeout": request_config.prepared_timeout,
|
|
1003
|
-
"verify": request_config.tls_verify,
|
|
1004
|
-
"cert": request_config.cert,
|
|
1005
|
-
}
|
|
1006
|
-
if request_config.proxy is not None:
|
|
1007
|
-
kwargs["proxies"] = {"all": request_config.proxy}
|
|
1008
|
-
hooks.dispatch("process_call_kwargs", hook_context, case, kwargs)
|
|
1009
1014
|
response = case.call(**kwargs)
|
|
1010
1015
|
except CheckFailed as exc:
|
|
1011
1016
|
check_name = "request_timeout"
|
|
@@ -1030,6 +1035,7 @@ def _network_test(
|
|
|
1030
1035
|
auth=ctx.auth,
|
|
1031
1036
|
headers=CaseInsensitiveDict(headers) if headers else None,
|
|
1032
1037
|
config=ctx.checks_config,
|
|
1038
|
+
transport_kwargs=kwargs,
|
|
1033
1039
|
)
|
|
1034
1040
|
try:
|
|
1035
1041
|
run_checks(
|
|
@@ -1041,6 +1047,7 @@ def _network_test(
|
|
|
1041
1047
|
response=response,
|
|
1042
1048
|
elapsed_time=context.response_time * 1000,
|
|
1043
1049
|
max_response_time=max_response_time,
|
|
1050
|
+
no_failfast=ctx.no_failfast,
|
|
1044
1051
|
)
|
|
1045
1052
|
except CheckFailed:
|
|
1046
1053
|
status = Status.failure
|
|
@@ -1127,6 +1134,7 @@ def _wsgi_test(
|
|
|
1127
1134
|
auth=ctx.auth,
|
|
1128
1135
|
headers=CaseInsensitiveDict(headers) if headers else None,
|
|
1129
1136
|
config=ctx.checks_config,
|
|
1137
|
+
transport_kwargs=kwargs,
|
|
1130
1138
|
)
|
|
1131
1139
|
try:
|
|
1132
1140
|
run_checks(
|
|
@@ -1138,6 +1146,7 @@ def _wsgi_test(
|
|
|
1138
1146
|
response=response,
|
|
1139
1147
|
elapsed_time=context.response_time * 1000,
|
|
1140
1148
|
max_response_time=max_response_time,
|
|
1149
|
+
no_failfast=ctx.no_failfast,
|
|
1141
1150
|
)
|
|
1142
1151
|
except CheckFailed:
|
|
1143
1152
|
status = Status.failure
|
|
@@ -1212,6 +1221,7 @@ def _asgi_test(
|
|
|
1212
1221
|
auth=ctx.auth,
|
|
1213
1222
|
headers=CaseInsensitiveDict(headers) if headers else None,
|
|
1214
1223
|
config=ctx.checks_config,
|
|
1224
|
+
transport_kwargs=kwargs,
|
|
1215
1225
|
)
|
|
1216
1226
|
try:
|
|
1217
1227
|
run_checks(
|
|
@@ -1223,6 +1233,7 @@ def _asgi_test(
|
|
|
1223
1233
|
response=response,
|
|
1224
1234
|
elapsed_time=context.response_time * 1000,
|
|
1225
1235
|
max_response_time=max_response_time,
|
|
1236
|
+
no_failfast=ctx.no_failfast,
|
|
1226
1237
|
)
|
|
1227
1238
|
except CheckFailed:
|
|
1228
1239
|
status = Status.failure
|
|
@@ -83,11 +83,14 @@ class GraphQLCase(Case):
|
|
|
83
83
|
excluded_checks: tuple[CheckFunction, ...] = (),
|
|
84
84
|
code_sample_style: str | None = None,
|
|
85
85
|
headers: dict[str, Any] | None = None,
|
|
86
|
+
transport_kwargs: dict[str, Any] | None = None,
|
|
86
87
|
) -> None:
|
|
87
88
|
checks = checks or (not_a_server_error,)
|
|
88
89
|
checks += additional_checks
|
|
89
90
|
checks = tuple(check for check in checks if check not in excluded_checks)
|
|
90
|
-
return super().validate_response(
|
|
91
|
+
return super().validate_response(
|
|
92
|
+
response, checks, code_sample_style=code_sample_style, headers=headers, transport_kwargs=transport_kwargs
|
|
93
|
+
)
|
|
91
94
|
|
|
92
95
|
|
|
93
96
|
C = TypeVar("C", bound=Case)
|
|
@@ -404,14 +404,21 @@ def ignored_auth(ctx: CheckContext, response: GenericResponse, case: Case) -> bo
|
|
|
404
404
|
# Auth is explicitly set, it is expected to be valid
|
|
405
405
|
# Check if invalid auth will give an error
|
|
406
406
|
_remove_auth_from_case(case, security_parameters)
|
|
407
|
-
|
|
407
|
+
kwargs = ctx.transport_kwargs or {}
|
|
408
|
+
kwargs.copy()
|
|
409
|
+
if "headers" in kwargs:
|
|
410
|
+
headers = kwargs["headers"].copy()
|
|
411
|
+
_remove_auth_from_explicit_headers(headers, security_parameters)
|
|
412
|
+
kwargs["headers"] = headers
|
|
413
|
+
kwargs.pop("session", None)
|
|
414
|
+
new_response = case.operation.schema.transport.send(case, **kwargs)
|
|
408
415
|
if new_response.status_code != 401:
|
|
409
416
|
_update_response(response, new_response)
|
|
410
417
|
_raise_no_auth_error(new_response, case.operation.verbose_name, "that requires authentication")
|
|
411
418
|
# Try to set invalid auth and check if it succeeds
|
|
412
419
|
for parameter in security_parameters:
|
|
413
420
|
_set_auth_for_case(case, parameter)
|
|
414
|
-
new_response = case.operation.schema.transport.send(case)
|
|
421
|
+
new_response = case.operation.schema.transport.send(case, **kwargs)
|
|
415
422
|
if new_response.status_code != 401:
|
|
416
423
|
_update_response(response, new_response)
|
|
417
424
|
_raise_no_auth_error(new_response, case.operation.verbose_name, "with any auth")
|
|
@@ -526,6 +533,13 @@ def _remove_auth_from_case(case: Case, security_parameters: list[SecurityParamet
|
|
|
526
533
|
case.cookies.pop(name, None)
|
|
527
534
|
|
|
528
535
|
|
|
536
|
+
def _remove_auth_from_explicit_headers(headers: dict, security_parameters: list[SecurityParameter]) -> None:
|
|
537
|
+
for parameter in security_parameters:
|
|
538
|
+
name = parameter["name"]
|
|
539
|
+
if parameter["in"] == "header":
|
|
540
|
+
headers.pop(name, None)
|
|
541
|
+
|
|
542
|
+
|
|
529
543
|
def _set_auth_for_case(case: Case, parameter: SecurityParameter) -> None:
|
|
530
544
|
name = parameter["name"]
|
|
531
545
|
for location, attr_name in (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: schemathesis
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.39.0
|
|
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
|
|
@@ -9,7 +9,8 @@ Project-URL: Funding, https://github.com/sponsors/Stranger6667
|
|
|
9
9
|
Project-URL: Source Code, https://github.com/schemathesis/schemathesis
|
|
10
10
|
Author-email: Dmitry Dygalo <dmitry@dygalo.dev>
|
|
11
11
|
Maintainer-email: Dmitry Dygalo <dmitry@dygalo.dev>
|
|
12
|
-
License: MIT
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
License-File: LICENSE
|
|
13
14
|
Keywords: graphql,hypothesis,openapi,pytest,testing
|
|
14
15
|
Classifier: Development Status :: 5 - Production/Stable
|
|
15
16
|
Classifier: Environment :: Console
|
|
@@ -6,7 +6,7 @@ schemathesis/_lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,
|
|
|
6
6
|
schemathesis/_override.py,sha256=TAjYB3eJQmlw9K_xiR9ptt9Wj7if4U7UFlUhGjpBAoM,1625
|
|
7
7
|
schemathesis/_patches.py,sha256=Hsbpn4UVeXUQD2Kllrbq01CSWsTYENWa0VJTyhX5C2k,895
|
|
8
8
|
schemathesis/_rate_limiter.py,sha256=q_XWst5hzuAyXQRiZc4s_bx7-JlPYZM_yKDmeavt3oo,242
|
|
9
|
-
schemathesis/_xml.py,sha256=
|
|
9
|
+
schemathesis/_xml.py,sha256=_R8h8dn2VepX8EywGnQZOjLw8qg5uIjHEHll4G_BkN8,8467
|
|
10
10
|
schemathesis/auths.py,sha256=De97IS_iOlC36-jRhkZ2DUndjUpXYgsd8R-nA-iHn88,16837
|
|
11
11
|
schemathesis/checks.py,sha256=YPUI1N5giGBy1072vd77e6HWelGAKrJUmJLEG4oqfF8,2630
|
|
12
12
|
schemathesis/code_samples.py,sha256=rsdTo6ksyUs3ZMhqx0mmmkPSKUCFa--snIOYsXgZd80,4120
|
|
@@ -18,7 +18,7 @@ schemathesis/graphql.py,sha256=XiuKcfoOB92iLFC8zpz2msLkM0_V0TLdxPNBqrrGZ8w,216
|
|
|
18
18
|
schemathesis/hooks.py,sha256=p5AXgjVGtka0jn9MOeyBaRUtNbqZTs4iaJqytYTacHc,14856
|
|
19
19
|
schemathesis/lazy.py,sha256=Ddhkk7Tpc_VcRGYkCtKDmP2gpjxVmEZ3b01ZTNjbm8I,19004
|
|
20
20
|
schemathesis/loaders.py,sha256=MoEhcdOEBJxNRn5X-ZNhWB9jZDHQQNpkNfEdQjf_NDw,4590
|
|
21
|
-
schemathesis/models.py,sha256=
|
|
21
|
+
schemathesis/models.py,sha256=QNeK1sGcNAhrJ452YZoLZIRsOHS6cOt1AYFLJhBPcJs,49962
|
|
22
22
|
schemathesis/parameters.py,sha256=izlu4MFYT1RWrC4RBxrV6weeCal-ODbdLQLMb0PYCZY,2327
|
|
23
23
|
schemathesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
24
|
schemathesis/sanitization.py,sha256=Lycn1VVfula9B6XpzkxTHja7CZ7RHqbUh9kBic0Yi4M,9056
|
|
@@ -28,7 +28,7 @@ schemathesis/targets.py,sha256=XIGRghvEzbmEJjse9aZgNEj67L3jAbiazm2rxURWgDE,2351
|
|
|
28
28
|
schemathesis/throttling.py,sha256=aisUc4MJDGIOGUAs9L2DlWWpdd4KyAFuNVKhYoaUC9M,1719
|
|
29
29
|
schemathesis/types.py,sha256=Tem2Q_zyMCd0Clp5iGKv6Fu13wdcbxVE8tCVH9WWt7A,1065
|
|
30
30
|
schemathesis/utils.py,sha256=LwqxqoAKmRiAdj-qUbNmgQgsamc49V5lc5TnOIDuuMA,4904
|
|
31
|
-
schemathesis/cli/__init__.py,sha256=
|
|
31
|
+
schemathesis/cli/__init__.py,sha256=rPhFfXv1_RTwJZTpCYLloQ7H-TZCgH4D8-jb8nQ-N_0,75636
|
|
32
32
|
schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
|
|
33
33
|
schemathesis/cli/callbacks.py,sha256=-VA_I_mVma9WxFNtUR8d2KNICKJD5ScayfSdKKPEP5Y,16321
|
|
34
34
|
schemathesis/cli/cassettes.py,sha256=zji-B-uuwyr0Z0BzQX-DLMV6lWb58JtLExcUE1v3m4Y,20153
|
|
@@ -63,7 +63,7 @@ schemathesis/generation/_hypothesis.py,sha256=74fzLPHugZgMQXerWYFAMqCAjtAXz5E4ge
|
|
|
63
63
|
schemathesis/generation/_methods.py,sha256=r8oVlJ71_gXcnEhU-byw2E0R2RswQQFm8U7yGErSqbw,1204
|
|
64
64
|
schemathesis/generation/coverage.py,sha256=XgT1yX6iy__qEXN3lFs0PYZkFwXFHAgJf7ow3nmjcDc,39243
|
|
65
65
|
schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
|
|
66
|
-
schemathesis/internal/checks.py,sha256=
|
|
66
|
+
schemathesis/internal/checks.py,sha256=_9fDm9w7rKzdztXYH7FsLwW2DVn3L1gzsPhJJ9o2qQQ,2713
|
|
67
67
|
schemathesis/internal/copy.py,sha256=DcL56z-d69kKR_5u8mlHvjSL1UTyUKNMAwexrwHFY1s,1031
|
|
68
68
|
schemathesis/internal/datetime.py,sha256=zPLBL0XXLNfP-KYel3H2m8pnsxjsA_4d-zTOhJg2EPQ,136
|
|
69
69
|
schemathesis/internal/deprecation.py,sha256=Ty5VBFBlufkITpP0WWTPIPbnB7biDi0kQgXVYWZp820,1273
|
|
@@ -74,13 +74,13 @@ schemathesis/internal/output.py,sha256=zMaG5knIuBieVH8CrcmPJgbmQukDs2xdekX0BrK7B
|
|
|
74
74
|
schemathesis/internal/result.py,sha256=d449YvyONjqjDs-A5DAPgtAI96iT753K8sU6_1HLo2Q,461
|
|
75
75
|
schemathesis/internal/transformation.py,sha256=M5LA4pFZC4nD_0iGfih1wLF3_q8xJas94Uuaymt-7Cw,690
|
|
76
76
|
schemathesis/internal/validation.py,sha256=G7i8jIMUpAeOnDsDF_eWYvRZe_yMprRswx0QAtMPyEw,966
|
|
77
|
-
schemathesis/runner/__init__.py,sha256=
|
|
77
|
+
schemathesis/runner/__init__.py,sha256=r8SoHc3X_wk5lfmt9P87gzv6nvbIowkQ2-WlT7fhReY,22182
|
|
78
78
|
schemathesis/runner/events.py,sha256=cRKKSDvHvKLBIyFBz-J0JtAKshbGGKco9eaMyLCgzsY,11734
|
|
79
79
|
schemathesis/runner/probes.py,sha256=no5AfO3kse25qvHevjeUfB0Q3C860V2AYzschUW3QMQ,5688
|
|
80
80
|
schemathesis/runner/serialization.py,sha256=vZi1wd9HX9Swp9VJ_hZFeDgy3Y726URpHra-TbPvQhk,20762
|
|
81
81
|
schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
|
|
82
|
-
schemathesis/runner/impl/context.py,sha256=
|
|
83
|
-
schemathesis/runner/impl/core.py,sha256=
|
|
82
|
+
schemathesis/runner/impl/context.py,sha256=oEdkXnlibVDobDRCMliImQwtX5RPEKgVEwVBCN67mfE,3132
|
|
83
|
+
schemathesis/runner/impl/core.py,sha256=aUeJxW3cvfi5IYwU2GqhDfcKrCK3GtMnsts-qyaIXGQ,48086
|
|
84
84
|
schemathesis/runner/impl/solo.py,sha256=y5QSxgK8nBCEjZVD5BpFvYUXmB6tEjk6TwxAo__NejA,2911
|
|
85
85
|
schemathesis/runner/impl/threadpool.py,sha256=yNR5LYE8f3N_4t42OwSgy0_qdGgBPM7d11F9c9oEAAs,15075
|
|
86
86
|
schemathesis/service/__init__.py,sha256=cDVTCFD1G-vvhxZkJUwiToTAEQ-0ByIoqwXvJBCf_V8,472
|
|
@@ -102,12 +102,12 @@ schemathesis/specs/graphql/_cache.py,sha256=7ras3q_InDJBPykgHroerl9f2jFamC8xJD35
|
|
|
102
102
|
schemathesis/specs/graphql/loaders.py,sha256=-PUdzGiGo1BZWe9slaUmqeamMqCM6daq2lSrFblomCc,12339
|
|
103
103
|
schemathesis/specs/graphql/nodes.py,sha256=bE3G1kNmqJ8OV4igBvIK-UORrkQA6Nofduf87O3TD9I,541
|
|
104
104
|
schemathesis/specs/graphql/scalars.py,sha256=9tvLTiYVe8A_E8ASA0czz3Z0Mp9lyak7R4wHpAE_jKo,1805
|
|
105
|
-
schemathesis/specs/graphql/schemas.py,sha256=
|
|
105
|
+
schemathesis/specs/graphql/schemas.py,sha256=6oTsg730KBswaeZK9EttVCxxk8FWV1JHjyiVzU6qXlQ,14445
|
|
106
106
|
schemathesis/specs/graphql/validation.py,sha256=uINIOt-2E7ZuQV2CxKzwez-7L9tDtqzMSpnVoRWvxy0,1635
|
|
107
107
|
schemathesis/specs/openapi/__init__.py,sha256=HDcx3bqpa6qWPpyMrxAbM3uTo0Lqpg-BUNZhDJSJKnw,279
|
|
108
108
|
schemathesis/specs/openapi/_cache.py,sha256=PAiAu4X_a2PQgD2lG5H3iisXdyg4SaHpU46bRZvfNkM,4320
|
|
109
109
|
schemathesis/specs/openapi/_hypothesis.py,sha256=nU8UDn1PzGCre4IVmwIuO9-CZv1KJe1fYY0d2BojhSo,22981
|
|
110
|
-
schemathesis/specs/openapi/checks.py,sha256=
|
|
110
|
+
schemathesis/specs/openapi/checks.py,sha256=VqrgvUbD8dUULxozjcuHZqP6sLrhEeP47Rz2EAQz84Q,25104
|
|
111
111
|
schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
|
|
112
112
|
schemathesis/specs/openapi/converter.py,sha256=Yxw9lS_JKEyi-oJuACT07fm04bqQDlAu-iHwzkeDvE4,3546
|
|
113
113
|
schemathesis/specs/openapi/definitions.py,sha256=WTkWwCgTc3OMxfKsqh6YDoGfZMTThSYrHGp8h0vLAK0,93935
|
|
@@ -153,8 +153,8 @@ schemathesis/transports/auth.py,sha256=urSTO9zgFO1qU69xvnKHPFQV0SlJL3d7_Ojl0tLnZ
|
|
|
153
153
|
schemathesis/transports/content_types.py,sha256=MiKOm-Hy5i75hrROPdpiBZPOTDzOwlCdnthJD12AJzI,2187
|
|
154
154
|
schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
|
|
155
155
|
schemathesis/transports/responses.py,sha256=OFD4ZLqwEFpo7F9vaP_SVgjhxAqatxIj38FS4XVq8Qs,1680
|
|
156
|
-
schemathesis-3.
|
|
157
|
-
schemathesis-3.
|
|
158
|
-
schemathesis-3.
|
|
159
|
-
schemathesis-3.
|
|
160
|
-
schemathesis-3.
|
|
156
|
+
schemathesis-3.39.0.dist-info/METADATA,sha256=ZFA8TF6uMkvkRtwPmSkjI0UrgLVOsuZHLqdikcGw6gk,12956
|
|
157
|
+
schemathesis-3.39.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
158
|
+
schemathesis-3.39.0.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
|
|
159
|
+
schemathesis-3.39.0.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
|
|
160
|
+
schemathesis-3.39.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|