schemathesis 3.35.2__py3-none-any.whl → 3.35.3__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/_hypothesis.py +10 -10
- schemathesis/cli/__init__.py +115 -98
- schemathesis/generation/coverage.py +41 -14
- schemathesis/models.py +23 -2
- schemathesis/runner/events.py +3 -0
- schemathesis/runner/impl/core.py +1 -2
- schemathesis/runner/serialization.py +10 -2
- schemathesis/specs/openapi/links.py +11 -2
- schemathesis/specs/openapi/parameters.py +14 -9
- {schemathesis-3.35.2.dist-info → schemathesis-3.35.3.dist-info}/METADATA +1 -1
- {schemathesis-3.35.2.dist-info → schemathesis-3.35.3.dist-info}/RECORD +14 -14
- {schemathesis-3.35.2.dist-info → schemathesis-3.35.3.dist-info}/WHEEL +0 -0
- {schemathesis-3.35.2.dist-info → schemathesis-3.35.3.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.35.2.dist-info → schemathesis-3.35.3.dist-info}/licenses/LICENSE +0 -0
schemathesis/_hypothesis.py
CHANGED
|
@@ -50,17 +50,17 @@ def create_test(
|
|
|
50
50
|
auth_storage = get_auth_storage_from_test(test)
|
|
51
51
|
strategies = []
|
|
52
52
|
skip_on_not_negated = len(data_generation_methods) == 1 and DataGenerationMethod.negative in data_generation_methods
|
|
53
|
+
as_strategy_kwargs = as_strategy_kwargs or {}
|
|
54
|
+
as_strategy_kwargs.update(
|
|
55
|
+
{
|
|
56
|
+
"hooks": hook_dispatcher,
|
|
57
|
+
"auth_storage": auth_storage,
|
|
58
|
+
"generation_config": generation_config,
|
|
59
|
+
"skip_on_not_negated": skip_on_not_negated,
|
|
60
|
+
}
|
|
61
|
+
)
|
|
53
62
|
for data_generation_method in data_generation_methods:
|
|
54
|
-
strategies.append(
|
|
55
|
-
operation.as_strategy(
|
|
56
|
-
hooks=hook_dispatcher,
|
|
57
|
-
auth_storage=auth_storage,
|
|
58
|
-
data_generation_method=data_generation_method,
|
|
59
|
-
generation_config=generation_config,
|
|
60
|
-
skip_on_not_negated=skip_on_not_negated,
|
|
61
|
-
**(as_strategy_kwargs or {}),
|
|
62
|
-
)
|
|
63
|
-
)
|
|
63
|
+
strategies.append(operation.as_strategy(data_generation_method=data_generation_method, **as_strategy_kwargs))
|
|
64
64
|
strategy = combine_strategies(strategies)
|
|
65
65
|
_given_kwargs = (_given_kwargs or {}).copy()
|
|
66
66
|
_given_kwargs.setdefault("case", strategy)
|
schemathesis/cli/__init__.py
CHANGED
|
@@ -125,10 +125,10 @@ def reset_targets() -> None:
|
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
@click.group(context_settings=CONTEXT_SETTINGS)
|
|
128
|
-
@click.option("--pre-run", help="A module to execute before running the tests
|
|
128
|
+
@click.option("--pre-run", help="[DEPRECATED] A module to execute before running the tests", type=str, hidden=True)
|
|
129
129
|
@click.version_option()
|
|
130
130
|
def schemathesis(pre_run: str | None = None) -> None:
|
|
131
|
-
"""
|
|
131
|
+
"""Property-based API testing for OpenAPI and GraphQL."""
|
|
132
132
|
# Don't use `envvar=HOOKS_MODULE_ENV_VAR` arg to raise a deprecation warning for hooks
|
|
133
133
|
hooks: str | None
|
|
134
134
|
if pre_run:
|
|
@@ -149,11 +149,18 @@ class CommandWithGroupedOptions(click.Command):
|
|
|
149
149
|
for param in self.get_params(ctx):
|
|
150
150
|
rv = param.get_help_record(ctx)
|
|
151
151
|
if rv is not None:
|
|
152
|
+
(option_repr, message) = rv
|
|
153
|
+
if isinstance(param.type, click.Choice):
|
|
154
|
+
message += (
|
|
155
|
+
getattr(param.type, "choices_repr", None)
|
|
156
|
+
or f" [possible values: {', '.join(param.type.choices)}]"
|
|
157
|
+
)
|
|
158
|
+
|
|
152
159
|
if isinstance(param, GroupedOption):
|
|
153
160
|
group = param.group
|
|
154
161
|
else:
|
|
155
162
|
group = "Global options"
|
|
156
|
-
groups[group].append(
|
|
163
|
+
groups[group].append((option_repr, message))
|
|
157
164
|
for group in GROUPS:
|
|
158
165
|
with formatter.section(group or "Options"):
|
|
159
166
|
formatter.write_dl(groups[group], col_max=40)
|
|
@@ -185,12 +192,12 @@ def grouped_option(*args: Any, **kwargs: Any) -> Callable:
|
|
|
185
192
|
|
|
186
193
|
with_request_proxy = grouped_option(
|
|
187
194
|
"--request-proxy",
|
|
188
|
-
help="Set the proxy for all network requests
|
|
195
|
+
help="Set the proxy for all network requests",
|
|
189
196
|
type=str,
|
|
190
197
|
)
|
|
191
198
|
with_request_tls_verify = grouped_option(
|
|
192
199
|
"--request-tls-verify",
|
|
193
|
-
help="Configures TLS certificate verification for server requests. Can specify path to CA_BUNDLE for custom certs
|
|
200
|
+
help="Configures TLS certificate verification for server requests. Can specify path to CA_BUNDLE for custom certs",
|
|
194
201
|
type=str,
|
|
195
202
|
default="true",
|
|
196
203
|
show_default=True,
|
|
@@ -200,14 +207,14 @@ with_request_cert = grouped_option(
|
|
|
200
207
|
"--request-cert",
|
|
201
208
|
help="File path of unencrypted client certificate for authentication. "
|
|
202
209
|
"The certificate can be bundled with a private key (e.g. PEM) or the private "
|
|
203
|
-
"key can be provided with the --request-cert-key argument
|
|
210
|
+
"key can be provided with the --request-cert-key argument",
|
|
204
211
|
type=click.Path(exists=True),
|
|
205
212
|
default=None,
|
|
206
213
|
show_default=False,
|
|
207
214
|
)
|
|
208
215
|
with_request_cert_key = grouped_option(
|
|
209
216
|
"--request-cert-key",
|
|
210
|
-
help="
|
|
217
|
+
help="Specify the file path of the private key for the client certificate",
|
|
211
218
|
type=click.Path(exists=True),
|
|
212
219
|
default=None,
|
|
213
220
|
show_default=False,
|
|
@@ -215,7 +222,7 @@ with_request_cert_key = grouped_option(
|
|
|
215
222
|
)
|
|
216
223
|
with_hosts_file = grouped_option(
|
|
217
224
|
"--hosts-file",
|
|
218
|
-
help="Path to a file to store the Schemathesis.io auth configuration
|
|
225
|
+
help="Path to a file to store the Schemathesis.io auth configuration",
|
|
219
226
|
type=click.Path(dir_okay=False, writable=True),
|
|
220
227
|
default=service.DEFAULT_HOSTS_PATH,
|
|
221
228
|
envvar=service.HOSTS_PATH_ENV_VAR,
|
|
@@ -262,7 +269,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
262
269
|
|
|
263
270
|
|
|
264
271
|
@schemathesis.command(
|
|
265
|
-
short_help="Execute automated tests based on API specifications
|
|
272
|
+
short_help="Execute automated tests based on API specifications",
|
|
266
273
|
cls=CommandWithGroupedOptions,
|
|
267
274
|
context_settings={"terminal_width": output.default.get_terminal_width(), **CONTEXT_SETTINGS},
|
|
268
275
|
)
|
|
@@ -273,26 +280,27 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
273
280
|
"--workers",
|
|
274
281
|
"-w",
|
|
275
282
|
"workers_num",
|
|
276
|
-
help="
|
|
283
|
+
help="Number of concurrent workers for testing. Auto-adjusts if 'auto' is specified",
|
|
277
284
|
type=CustomHelpMessageChoice(
|
|
278
285
|
["auto"] + list(map(str, range(MIN_WORKERS, MAX_WORKERS + 1))),
|
|
279
|
-
choices_repr=f"[auto
|
|
286
|
+
choices_repr=f"[auto, {MIN_WORKERS}-{MAX_WORKERS}]",
|
|
280
287
|
),
|
|
281
288
|
default=str(DEFAULT_WORKERS),
|
|
282
289
|
show_default=True,
|
|
283
290
|
callback=callbacks.convert_workers,
|
|
291
|
+
metavar="",
|
|
284
292
|
)
|
|
285
293
|
@grouped_option(
|
|
286
294
|
"--dry-run",
|
|
287
295
|
"dry_run",
|
|
288
296
|
is_flag=True,
|
|
289
297
|
default=False,
|
|
290
|
-
help="
|
|
298
|
+
help="Simulate test execution without making any actual requests, useful for validating data generation",
|
|
291
299
|
)
|
|
292
300
|
@grouped_option(
|
|
293
301
|
"--experimental",
|
|
294
302
|
"experiments",
|
|
295
|
-
help="Enable experimental features
|
|
303
|
+
help="Enable experimental features",
|
|
296
304
|
type=click.Choice(
|
|
297
305
|
[
|
|
298
306
|
experimental.OPEN_API_3_1.name,
|
|
@@ -304,41 +312,41 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
304
312
|
),
|
|
305
313
|
callback=callbacks.convert_experimental,
|
|
306
314
|
multiple=True,
|
|
315
|
+
metavar="",
|
|
307
316
|
)
|
|
308
317
|
@grouped_option(
|
|
309
318
|
"--fixups",
|
|
310
|
-
help="
|
|
319
|
+
help="Apply compatibility adjustments",
|
|
311
320
|
multiple=True,
|
|
312
321
|
type=click.Choice(list(ALL_FIXUPS) + ["all"]),
|
|
322
|
+
metavar="",
|
|
313
323
|
)
|
|
314
324
|
@group("API validation options")
|
|
315
325
|
@grouped_option(
|
|
316
326
|
"--checks",
|
|
317
327
|
"-c",
|
|
318
328
|
multiple=True,
|
|
319
|
-
help="
|
|
320
|
-
"Provide a comma-separated list of checks such as 'not_a_server_error,status_code_conformance', etc. "
|
|
321
|
-
f"Default is '{','.join(DEFAULT_CHECKS_NAMES)}'.",
|
|
329
|
+
help="Comma-separated list of checks to run against API responses",
|
|
322
330
|
type=CHECKS_TYPE,
|
|
323
331
|
default=DEFAULT_CHECKS_NAMES,
|
|
324
332
|
callback=callbacks.convert_checks,
|
|
325
333
|
show_default=True,
|
|
334
|
+
metavar="",
|
|
326
335
|
)
|
|
327
336
|
@grouped_option(
|
|
328
337
|
"--exclude-checks",
|
|
329
338
|
multiple=True,
|
|
330
|
-
help="
|
|
331
|
-
"Provide a comma-separated list of checks you wish to bypass.",
|
|
339
|
+
help="Comma-separated list of checks to skip during testing",
|
|
332
340
|
type=EXCLUDE_CHECKS_TYPE,
|
|
333
341
|
default=[],
|
|
334
342
|
callback=callbacks.convert_checks,
|
|
335
343
|
show_default=True,
|
|
344
|
+
metavar="",
|
|
336
345
|
)
|
|
337
346
|
@grouped_option(
|
|
338
347
|
"--max-response-time",
|
|
339
|
-
help="
|
|
340
|
-
"The test will fail if a response time exceeds this limit. "
|
|
341
|
-
"Provide the time in milliseconds.",
|
|
348
|
+
help="Time limit in milliseconds for API response times. "
|
|
349
|
+
"The test will fail if a response time exceeds this limit. ",
|
|
342
350
|
type=click.IntRange(min=1),
|
|
343
351
|
)
|
|
344
352
|
@grouped_option(
|
|
@@ -347,33 +355,33 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
347
355
|
"exit_first",
|
|
348
356
|
is_flag=True,
|
|
349
357
|
default=False,
|
|
350
|
-
help="
|
|
358
|
+
help="Terminate the test suite immediately upon the first failure or error encountered",
|
|
351
359
|
show_default=True,
|
|
352
360
|
)
|
|
353
361
|
@grouped_option(
|
|
354
362
|
"--max-failures",
|
|
355
363
|
"max_failures",
|
|
356
364
|
type=click.IntRange(min=1),
|
|
357
|
-
help="
|
|
365
|
+
help="Terminate the test suite after reaching a specified number of failures or errors",
|
|
358
366
|
show_default=True,
|
|
359
367
|
)
|
|
360
368
|
@group("Loader options")
|
|
361
369
|
@grouped_option(
|
|
362
370
|
"--app",
|
|
363
|
-
help="
|
|
371
|
+
help="Specify the WSGI/ASGI application under test, provided as an importable Python path",
|
|
364
372
|
type=str,
|
|
365
373
|
callback=callbacks.validate_app,
|
|
366
374
|
)
|
|
367
375
|
@grouped_option(
|
|
368
376
|
"--wait-for-schema",
|
|
369
|
-
help="Maximum duration, in seconds, to wait for the API schema to become available. Disabled by default
|
|
377
|
+
help="Maximum duration, in seconds, to wait for the API schema to become available. Disabled by default",
|
|
370
378
|
type=click.FloatRange(1.0),
|
|
371
379
|
default=None,
|
|
372
380
|
envvar=WAIT_FOR_SCHEMA_ENV_VAR,
|
|
373
381
|
)
|
|
374
382
|
@grouped_option(
|
|
375
383
|
"--validate-schema",
|
|
376
|
-
help="
|
|
384
|
+
help="Validate input API schema. Set to 'true' to enable or 'false' to disable",
|
|
377
385
|
type=bool,
|
|
378
386
|
default=False,
|
|
379
387
|
show_default=True,
|
|
@@ -382,14 +390,14 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
382
390
|
@grouped_option(
|
|
383
391
|
"--base-url",
|
|
384
392
|
"-b",
|
|
385
|
-
help="
|
|
393
|
+
help="Base URL of the API, required when schema is provided as a file",
|
|
386
394
|
type=str,
|
|
387
395
|
callback=callbacks.validate_base_url,
|
|
388
396
|
envvar=BASE_URL_ENV_VAR,
|
|
389
397
|
)
|
|
390
398
|
@grouped_option(
|
|
391
399
|
"--request-timeout",
|
|
392
|
-
help="
|
|
400
|
+
help="Timeout limit, in milliseconds, for each network request during tests",
|
|
393
401
|
type=click.IntRange(1),
|
|
394
402
|
default=DEFAULT_RESPONSE_TIMEOUT,
|
|
395
403
|
)
|
|
@@ -399,8 +407,8 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
399
407
|
@with_request_cert_key
|
|
400
408
|
@grouped_option(
|
|
401
409
|
"--rate-limit",
|
|
402
|
-
help="
|
|
403
|
-
"Example - `100/m` for 100 requests per minute
|
|
410
|
+
help="Specify a rate limit for test requests in '<limit>/<duration>' format. "
|
|
411
|
+
"Example - `100/m` for 100 requests per minute",
|
|
404
412
|
type=str,
|
|
405
413
|
callback=callbacks.validate_rate_limit,
|
|
406
414
|
)
|
|
@@ -408,7 +416,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
408
416
|
"--header",
|
|
409
417
|
"-H",
|
|
410
418
|
"headers",
|
|
411
|
-
help=r"
|
|
419
|
+
help=r"Add a custom HTTP header to all API requests. Format: 'Header-Name: Value'",
|
|
412
420
|
multiple=True,
|
|
413
421
|
type=str,
|
|
414
422
|
callback=callbacks.validate_headers,
|
|
@@ -416,7 +424,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
416
424
|
@grouped_option(
|
|
417
425
|
"--auth",
|
|
418
426
|
"-a",
|
|
419
|
-
help="
|
|
427
|
+
help="Provide the server authentication details in the 'USER:PASSWORD' format",
|
|
420
428
|
type=str,
|
|
421
429
|
callback=callbacks.validate_auth,
|
|
422
430
|
)
|
|
@@ -425,8 +433,9 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
425
433
|
"-A",
|
|
426
434
|
type=click.Choice(["basic", "digest"], case_sensitive=False),
|
|
427
435
|
default="basic",
|
|
428
|
-
help="
|
|
436
|
+
help="Specify the authentication method",
|
|
429
437
|
show_default=True,
|
|
438
|
+
metavar="",
|
|
430
439
|
)
|
|
431
440
|
@group("Filtering options")
|
|
432
441
|
@with_filters
|
|
@@ -444,7 +453,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
444
453
|
)
|
|
445
454
|
@grouped_option(
|
|
446
455
|
"--exclude-deprecated",
|
|
447
|
-
help="Exclude deprecated API operations from testing
|
|
456
|
+
help="Exclude deprecated API operations from testing",
|
|
448
457
|
is_flag=True,
|
|
449
458
|
is_eager=True,
|
|
450
459
|
default=False,
|
|
@@ -456,7 +465,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
456
465
|
"endpoints",
|
|
457
466
|
type=str,
|
|
458
467
|
multiple=True,
|
|
459
|
-
help=r"[DEPRECATED] API operation path pattern (e.g., users/\d+)
|
|
468
|
+
help=r"[DEPRECATED] API operation path pattern (e.g., users/\d+)",
|
|
460
469
|
callback=callbacks.validate_regex,
|
|
461
470
|
hidden=True,
|
|
462
471
|
)
|
|
@@ -466,7 +475,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
466
475
|
"methods",
|
|
467
476
|
type=str,
|
|
468
477
|
multiple=True,
|
|
469
|
-
help="[DEPRECATED] HTTP method (e.g., GET, POST)
|
|
478
|
+
help="[DEPRECATED] HTTP method (e.g., GET, POST)",
|
|
470
479
|
callback=callbacks.validate_regex,
|
|
471
480
|
hidden=True,
|
|
472
481
|
)
|
|
@@ -476,7 +485,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
476
485
|
"tags",
|
|
477
486
|
type=str,
|
|
478
487
|
multiple=True,
|
|
479
|
-
help="[DEPRECATED] Schema tag pattern
|
|
488
|
+
help="[DEPRECATED] Schema tag pattern",
|
|
480
489
|
callback=callbacks.validate_regex,
|
|
481
490
|
hidden=True,
|
|
482
491
|
)
|
|
@@ -486,13 +495,13 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
486
495
|
"operation_ids",
|
|
487
496
|
type=str,
|
|
488
497
|
multiple=True,
|
|
489
|
-
help="[DEPRECATED] OpenAPI operationId pattern
|
|
498
|
+
help="[DEPRECATED] OpenAPI operationId pattern",
|
|
490
499
|
callback=callbacks.validate_regex,
|
|
491
500
|
hidden=True,
|
|
492
501
|
)
|
|
493
502
|
@grouped_option(
|
|
494
503
|
"--skip-deprecated-operations",
|
|
495
|
-
help="[DEPRECATED] Exclude deprecated API operations from testing
|
|
504
|
+
help="[DEPRECATED] Exclude deprecated API operations from testing",
|
|
496
505
|
is_flag=True,
|
|
497
506
|
is_eager=True,
|
|
498
507
|
default=False,
|
|
@@ -502,45 +511,47 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
502
511
|
@group("Output options")
|
|
503
512
|
@grouped_option(
|
|
504
513
|
"--junit-xml",
|
|
505
|
-
help="
|
|
514
|
+
help="Output a JUnit-XML style report at the specified file path",
|
|
506
515
|
type=click.File("w", encoding="utf-8"),
|
|
507
516
|
)
|
|
508
517
|
@grouped_option(
|
|
509
518
|
"--cassette-path",
|
|
510
|
-
help="
|
|
519
|
+
help="Save the test outcomes in a VCR-compatible format",
|
|
511
520
|
type=click.File("w", encoding="utf-8"),
|
|
512
521
|
is_eager=True,
|
|
513
522
|
)
|
|
514
523
|
@grouped_option(
|
|
515
524
|
"--cassette-format",
|
|
516
|
-
help="Format of the saved cassettes
|
|
525
|
+
help="Format of the saved cassettes",
|
|
517
526
|
type=click.Choice([item.name.lower() for item in cassettes.CassetteFormat]),
|
|
518
527
|
default=cassettes.CassetteFormat.VCR.name.lower(),
|
|
519
528
|
callback=callbacks.convert_cassette_format,
|
|
529
|
+
metavar="",
|
|
520
530
|
)
|
|
521
531
|
@grouped_option(
|
|
522
532
|
"--cassette-preserve-exact-body-bytes",
|
|
523
|
-
help="
|
|
533
|
+
help="Retain exact byte sequence of payloads in cassettes, encoded as base64",
|
|
524
534
|
is_flag=True,
|
|
525
535
|
callback=callbacks.validate_preserve_exact_body_bytes,
|
|
526
536
|
)
|
|
527
537
|
@grouped_option(
|
|
528
538
|
"--code-sample-style",
|
|
529
|
-
help="
|
|
539
|
+
help="Code sample style for reproducing failures",
|
|
530
540
|
type=click.Choice([item.name for item in CodeSampleStyle]),
|
|
531
541
|
default=CodeSampleStyle.default().name,
|
|
532
542
|
callback=callbacks.convert_code_sample_style,
|
|
543
|
+
metavar="",
|
|
533
544
|
)
|
|
534
545
|
@grouped_option(
|
|
535
546
|
"--sanitize-output",
|
|
536
547
|
type=bool,
|
|
537
548
|
default=True,
|
|
538
549
|
show_default=True,
|
|
539
|
-
help="Enable or disable automatic output sanitization to obscure sensitive data
|
|
550
|
+
help="Enable or disable automatic output sanitization to obscure sensitive data",
|
|
540
551
|
)
|
|
541
552
|
@grouped_option(
|
|
542
553
|
"--output-truncate",
|
|
543
|
-
help="
|
|
554
|
+
help="Truncate schemas and responses in error messages",
|
|
544
555
|
type=str,
|
|
545
556
|
default="true",
|
|
546
557
|
show_default=True,
|
|
@@ -548,7 +559,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
548
559
|
)
|
|
549
560
|
@grouped_option(
|
|
550
561
|
"--show-trace",
|
|
551
|
-
help="
|
|
562
|
+
help="Display complete traceback information for internal errors",
|
|
552
563
|
is_flag=True,
|
|
553
564
|
is_eager=True,
|
|
554
565
|
default=False,
|
|
@@ -556,18 +567,18 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
556
567
|
)
|
|
557
568
|
@grouped_option(
|
|
558
569
|
"--debug-output-file",
|
|
559
|
-
help="
|
|
570
|
+
help="Save debugging information in a JSONL format at the specified file path",
|
|
560
571
|
type=click.File("w", encoding="utf-8"),
|
|
561
572
|
)
|
|
562
573
|
@grouped_option(
|
|
563
574
|
"--store-network-log",
|
|
564
|
-
help="[DEPRECATED]
|
|
575
|
+
help="[DEPRECATED] Save the test outcomes in a VCR-compatible format",
|
|
565
576
|
type=click.File("w", encoding="utf-8"),
|
|
566
577
|
hidden=True,
|
|
567
578
|
)
|
|
568
579
|
@grouped_option(
|
|
569
580
|
"--show-errors-tracebacks",
|
|
570
|
-
help="[DEPRECATED]
|
|
581
|
+
help="[DEPRECATED] Display complete traceback information for internal errors",
|
|
571
582
|
is_flag=True,
|
|
572
583
|
is_eager=True,
|
|
573
584
|
default=False,
|
|
@@ -579,24 +590,25 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
579
590
|
"--data-generation-method",
|
|
580
591
|
"-D",
|
|
581
592
|
"data_generation_methods",
|
|
582
|
-
help="
|
|
583
|
-
"Use 'positive' for valid data, 'negative' for invalid data, or 'all' for both
|
|
584
|
-
"Default is 'positive'.",
|
|
593
|
+
help="Specify the approach Schemathesis uses to generate test data. "
|
|
594
|
+
"Use 'positive' for valid data, 'negative' for invalid data, or 'all' for both",
|
|
585
595
|
type=DATA_GENERATION_METHOD_TYPE,
|
|
586
596
|
default=DataGenerationMethod.default().name,
|
|
587
597
|
callback=callbacks.convert_data_generation_method,
|
|
588
598
|
show_default=True,
|
|
599
|
+
metavar="",
|
|
589
600
|
)
|
|
590
601
|
@grouped_option(
|
|
591
602
|
"--stateful",
|
|
592
|
-
help="
|
|
603
|
+
help="Enable or disable stateful testing",
|
|
593
604
|
type=click.Choice([item.name for item in Stateful]),
|
|
594
605
|
default=Stateful.links.name,
|
|
595
606
|
callback=callbacks.convert_stateful,
|
|
607
|
+
metavar="",
|
|
596
608
|
)
|
|
597
609
|
@grouped_option(
|
|
598
610
|
"--stateful-recursion-limit",
|
|
599
|
-
help="
|
|
611
|
+
help="Recursion depth limit for stateful testing",
|
|
600
612
|
default=DEFAULT_STATEFUL_RECURSION_LIMIT,
|
|
601
613
|
show_default=True,
|
|
602
614
|
type=click.IntRange(1, 100),
|
|
@@ -604,7 +616,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
604
616
|
)
|
|
605
617
|
@grouped_option(
|
|
606
618
|
"--generation-allow-x00",
|
|
607
|
-
help="
|
|
619
|
+
help="Whether to allow the generation of `\x00` bytes within strings",
|
|
608
620
|
type=str,
|
|
609
621
|
default="true",
|
|
610
622
|
show_default=True,
|
|
@@ -612,14 +624,14 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
612
624
|
)
|
|
613
625
|
@grouped_option(
|
|
614
626
|
"--generation-codec",
|
|
615
|
-
help="
|
|
627
|
+
help="The codec used for generating strings",
|
|
616
628
|
type=str,
|
|
617
629
|
default="utf-8",
|
|
618
630
|
callback=callbacks.validate_generation_codec,
|
|
619
631
|
)
|
|
620
632
|
@grouped_option(
|
|
621
633
|
"--generation-with-security-parameters",
|
|
622
|
-
help="Whether to generate security parameters
|
|
634
|
+
help="Whether to generate security parameters",
|
|
623
635
|
type=str,
|
|
624
636
|
default="true",
|
|
625
637
|
show_default=True,
|
|
@@ -627,7 +639,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
627
639
|
)
|
|
628
640
|
@grouped_option(
|
|
629
641
|
"--generation-graphql-allow-null",
|
|
630
|
-
help="Whether `null` values
|
|
642
|
+
help="Whether to use `null` values for optional arguments in GraphQL queries",
|
|
631
643
|
type=str,
|
|
632
644
|
default="true",
|
|
633
645
|
show_default=True,
|
|
@@ -636,7 +648,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
636
648
|
@grouped_option(
|
|
637
649
|
"--contrib-unique-data",
|
|
638
650
|
"contrib_unique_data",
|
|
639
|
-
help="
|
|
651
|
+
help="Force the generation of unique test cases",
|
|
640
652
|
is_flag=True,
|
|
641
653
|
default=False,
|
|
642
654
|
show_default=True,
|
|
@@ -644,7 +656,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
644
656
|
@grouped_option(
|
|
645
657
|
"--contrib-openapi-formats-uuid",
|
|
646
658
|
"contrib_openapi_formats_uuid",
|
|
647
|
-
help="
|
|
659
|
+
help="Enable support for the 'uuid' string format in OpenAPI",
|
|
648
660
|
is_flag=True,
|
|
649
661
|
default=False,
|
|
650
662
|
show_default=True,
|
|
@@ -652,7 +664,7 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
652
664
|
@grouped_option(
|
|
653
665
|
"--contrib-openapi-fill-missing-examples",
|
|
654
666
|
"contrib_openapi_fill_missing_examples",
|
|
655
|
-
help="
|
|
667
|
+
help="Enable generation of random examples for API operations that do not have explicit examples",
|
|
656
668
|
is_flag=True,
|
|
657
669
|
default=False,
|
|
658
670
|
show_default=True,
|
|
@@ -662,16 +674,18 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
662
674
|
"-t",
|
|
663
675
|
"targets",
|
|
664
676
|
multiple=True,
|
|
665
|
-
help="
|
|
677
|
+
help="Guide input generation to values more likely to expose bugs via targeted property-based testing",
|
|
666
678
|
type=TARGETS_TYPE,
|
|
667
679
|
default=DEFAULT_TARGETS_NAMES,
|
|
668
680
|
show_default=True,
|
|
681
|
+
metavar="",
|
|
669
682
|
)
|
|
670
683
|
@group("Open API options")
|
|
671
684
|
@grouped_option(
|
|
672
685
|
"--force-schema-version",
|
|
673
|
-
help="
|
|
686
|
+
help="Force the schema to be interpreted as a particular OpenAPI version",
|
|
674
687
|
type=click.Choice(["20", "30"]),
|
|
688
|
+
metavar="",
|
|
675
689
|
)
|
|
676
690
|
@grouped_option(
|
|
677
691
|
"--set-query",
|
|
@@ -708,21 +722,21 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
708
722
|
@group("Hypothesis engine options")
|
|
709
723
|
@grouped_option(
|
|
710
724
|
"--hypothesis-database",
|
|
711
|
-
help="
|
|
725
|
+
help="Storage for examples discovered by Hypothesis. "
|
|
712
726
|
f"Use 'none' to disable, '{HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER}' for temporary storage, "
|
|
713
|
-
f"or specify a file path for persistent storage
|
|
727
|
+
f"or specify a file path for persistent storage",
|
|
714
728
|
type=str,
|
|
715
729
|
callback=callbacks.validate_hypothesis_database,
|
|
716
730
|
)
|
|
717
731
|
@grouped_option(
|
|
718
732
|
"--hypothesis-deadline",
|
|
719
|
-
help="
|
|
720
|
-
"Exceeding this limit will cause the test to fail
|
|
733
|
+
help="Time limit for each test case generated by Hypothesis, in milliseconds. "
|
|
734
|
+
"Exceeding this limit will cause the test to fail",
|
|
721
735
|
type=OptionalInt(1, 5 * 60 * 1000),
|
|
722
736
|
)
|
|
723
737
|
@grouped_option(
|
|
724
738
|
"--hypothesis-derandomize",
|
|
725
|
-
help="Enables deterministic mode in Hypothesis, which eliminates random variation between
|
|
739
|
+
help="Enables deterministic mode in Hypothesis, which eliminates random variation between tests",
|
|
726
740
|
is_flag=True,
|
|
727
741
|
is_eager=True,
|
|
728
742
|
default=None,
|
|
@@ -730,49 +744,52 @@ REPORT_TO_SERVICE = ReportToService()
|
|
|
730
744
|
)
|
|
731
745
|
@grouped_option(
|
|
732
746
|
"--hypothesis-max-examples",
|
|
733
|
-
help="
|
|
747
|
+
help="The cap on the number of examples generated by Hypothesis for each API operation",
|
|
734
748
|
type=click.IntRange(1),
|
|
735
749
|
)
|
|
736
750
|
@grouped_option(
|
|
737
751
|
"--hypothesis-phases",
|
|
738
|
-
help="
|
|
752
|
+
help="Testing phases to execute",
|
|
739
753
|
type=CsvEnumChoice(Phase),
|
|
754
|
+
metavar="",
|
|
740
755
|
)
|
|
741
756
|
@grouped_option(
|
|
742
757
|
"--hypothesis-no-phases",
|
|
743
|
-
help="
|
|
758
|
+
help="Testing phases to exclude from execution",
|
|
744
759
|
type=CsvEnumChoice(Phase),
|
|
760
|
+
metavar="",
|
|
745
761
|
)
|
|
746
762
|
@grouped_option(
|
|
747
763
|
"--hypothesis-report-multiple-bugs",
|
|
748
|
-
help="
|
|
764
|
+
help="Report only the most easily reproducible error when multiple issues are found",
|
|
749
765
|
type=bool,
|
|
750
766
|
)
|
|
751
767
|
@grouped_option(
|
|
752
768
|
"--hypothesis-seed",
|
|
753
|
-
help="
|
|
769
|
+
help="Seed value for Hypothesis, ensuring reproducibility across test runs",
|
|
754
770
|
type=int,
|
|
755
771
|
)
|
|
756
772
|
@grouped_option(
|
|
757
773
|
"--hypothesis-suppress-health-check",
|
|
758
|
-
help="
|
|
759
|
-
"Provide a comma-separated list",
|
|
774
|
+
help="A comma-separated list of Hypothesis health checks to disable",
|
|
760
775
|
type=CsvEnumChoice(HealthCheck),
|
|
776
|
+
metavar="",
|
|
761
777
|
)
|
|
762
778
|
@grouped_option(
|
|
763
779
|
"--hypothesis-verbosity",
|
|
764
|
-
help="
|
|
780
|
+
help="Verbosity level of Hypothesis output",
|
|
765
781
|
type=click.Choice([item.name for item in Verbosity]),
|
|
766
782
|
callback=callbacks.convert_verbosity,
|
|
783
|
+
metavar="",
|
|
767
784
|
)
|
|
768
785
|
@group("Schemathesis.io options")
|
|
769
786
|
@grouped_option(
|
|
770
787
|
"--report",
|
|
771
788
|
"report_value",
|
|
772
|
-
help="""
|
|
789
|
+
help="""Specify how the generated report should be handled.
|
|
773
790
|
If used without an argument, the report data will automatically be uploaded to Schemathesis.io.
|
|
774
791
|
If a file name is provided, the report will be stored in that file.
|
|
775
|
-
The report data, consisting of a tar gz file with multiple JSON files, is subject to change
|
|
792
|
+
The report data, consisting of a tar gz file with multiple JSON files, is subject to change""",
|
|
776
793
|
is_flag=False,
|
|
777
794
|
flag_value="",
|
|
778
795
|
envvar=service.REPORT_ENV_VAR,
|
|
@@ -780,20 +797,20 @@ The report data, consisting of a tar gz file with multiple JSON files, is subjec
|
|
|
780
797
|
)
|
|
781
798
|
@grouped_option(
|
|
782
799
|
"--schemathesis-io-token",
|
|
783
|
-
help="Schemathesis.io authentication token
|
|
800
|
+
help="Schemathesis.io authentication token",
|
|
784
801
|
type=str,
|
|
785
802
|
envvar=service.TOKEN_ENV_VAR,
|
|
786
803
|
)
|
|
787
804
|
@grouped_option(
|
|
788
805
|
"--schemathesis-io-url",
|
|
789
|
-
help="Schemathesis.io base URL
|
|
806
|
+
help="Schemathesis.io base URL",
|
|
790
807
|
default=service.DEFAULT_URL,
|
|
791
808
|
type=str,
|
|
792
809
|
envvar=service.URL_ENV_VAR,
|
|
793
810
|
)
|
|
794
811
|
@grouped_option(
|
|
795
812
|
"--schemathesis-io-telemetry",
|
|
796
|
-
help="
|
|
813
|
+
help="Whether to send anonymized usage data to Schemathesis.io along with your report",
|
|
797
814
|
type=str,
|
|
798
815
|
default="true",
|
|
799
816
|
show_default=True,
|
|
@@ -802,9 +819,9 @@ The report data, consisting of a tar gz file with multiple JSON files, is subjec
|
|
|
802
819
|
)
|
|
803
820
|
@with_hosts_file
|
|
804
821
|
@group("Global options")
|
|
805
|
-
@grouped_option("--verbosity", "-v", help="Increase verbosity of the output
|
|
806
|
-
@grouped_option("--no-color", help="Disable ANSI color escape codes
|
|
807
|
-
@grouped_option("--force-color", help="Explicitly tells to enable ANSI color escape codes
|
|
822
|
+
@grouped_option("--verbosity", "-v", help="Increase verbosity of the output", count=True)
|
|
823
|
+
@grouped_option("--no-color", help="Disable ANSI color escape codes", type=bool, is_flag=True)
|
|
824
|
+
@grouped_option("--force-color", help="Explicitly tells to enable ANSI color escape codes", type=bool, is_flag=True)
|
|
808
825
|
@click.pass_context
|
|
809
826
|
def run(
|
|
810
827
|
ctx: click.Context,
|
|
@@ -909,9 +926,9 @@ def run(
|
|
|
909
926
|
) -> None:
|
|
910
927
|
"""Run tests against an API using a specified SCHEMA.
|
|
911
928
|
|
|
912
|
-
[Required] SCHEMA: Path to an OpenAPI (`.json`, `.yml`) or GraphQL SDL file, or a URL pointing to such specifications
|
|
929
|
+
[Required] SCHEMA: Path to an OpenAPI (`.json`, `.yml`) or GraphQL SDL file, or a URL pointing to such specifications
|
|
913
930
|
|
|
914
|
-
[Optional] API_NAME: Identifier for uploading test data to Schemathesis.io
|
|
931
|
+
[Optional] API_NAME: Identifier for uploading test data to Schemathesis.io
|
|
915
932
|
"""
|
|
916
933
|
_hypothesis_phases: list[hypothesis.Phase] | None = None
|
|
917
934
|
if hypothesis_phases is not None:
|
|
@@ -996,7 +1013,7 @@ def run(
|
|
|
996
1013
|
replacement = deprecated_filters[arg_name]
|
|
997
1014
|
click.secho(
|
|
998
1015
|
f"Warning: Option `{arg_name}` is deprecated and will be removed in Schemathesis 4.0. "
|
|
999
|
-
f"Use `{replacement}` instead
|
|
1016
|
+
f"Use `{replacement}` instead",
|
|
1000
1017
|
fg="yellow",
|
|
1001
1018
|
)
|
|
1002
1019
|
_ensure_unique_filter(values, arg_name)
|
|
@@ -1781,13 +1798,13 @@ def get_exit_code(event: events.ExecutionEvent) -> int:
|
|
|
1781
1798
|
|
|
1782
1799
|
@schemathesis.command(short_help="Replay requests from a saved cassette.")
|
|
1783
1800
|
@click.argument("cassette_path", type=click.Path(exists=True))
|
|
1784
|
-
@click.option("--id", "id_", help="ID of interaction to replay
|
|
1785
|
-
@click.option("--status", help="Status of interactions to replay
|
|
1786
|
-
@click.option("--uri", help="A regexp that filters interactions by their request URI
|
|
1787
|
-
@click.option("--method", help="A regexp that filters interactions by their request method
|
|
1788
|
-
@click.option("--no-color", help="Disable ANSI color escape codes
|
|
1789
|
-
@click.option("--force-color", help="Explicitly tells to enable ANSI color escape codes
|
|
1790
|
-
@click.option("--verbosity", "-v", help="Increase verbosity of the output
|
|
1801
|
+
@click.option("--id", "id_", help="ID of interaction to replay", type=str)
|
|
1802
|
+
@click.option("--status", help="Status of interactions to replay", type=str)
|
|
1803
|
+
@click.option("--uri", help="A regexp that filters interactions by their request URI", type=str)
|
|
1804
|
+
@click.option("--method", help="A regexp that filters interactions by their request method", type=str)
|
|
1805
|
+
@click.option("--no-color", help="Disable ANSI color escape codes", type=bool, is_flag=True)
|
|
1806
|
+
@click.option("--force-color", help="Explicitly tells to enable ANSI color escape codes", type=bool, is_flag=True)
|
|
1807
|
+
@click.option("--verbosity", "-v", help="Increase verbosity of the output", count=True)
|
|
1791
1808
|
@with_request_tls_verify
|
|
1792
1809
|
@with_request_proxy
|
|
1793
1810
|
@with_request_cert
|
|
@@ -1855,13 +1872,13 @@ def replay(
|
|
|
1855
1872
|
@click.argument("report", type=click.File(mode="rb"))
|
|
1856
1873
|
@click.option(
|
|
1857
1874
|
"--schemathesis-io-token",
|
|
1858
|
-
help="Schemathesis.io authentication token
|
|
1875
|
+
help="Schemathesis.io authentication token",
|
|
1859
1876
|
type=str,
|
|
1860
1877
|
envvar=service.TOKEN_ENV_VAR,
|
|
1861
1878
|
)
|
|
1862
1879
|
@click.option(
|
|
1863
1880
|
"--schemathesis-io-url",
|
|
1864
|
-
help="Schemathesis.io base URL
|
|
1881
|
+
help="Schemathesis.io base URL",
|
|
1865
1882
|
default=service.DEFAULT_URL,
|
|
1866
1883
|
type=str,
|
|
1867
1884
|
envvar=service.URL_ENV_VAR,
|
|
@@ -201,12 +201,12 @@ def cover_schema_iter(ctx: CoverageContext, schema: dict | bool) -> Generator[Ge
|
|
|
201
201
|
seen.add(value)
|
|
202
202
|
elif key == "multipleOf":
|
|
203
203
|
yield from _negative_multiple_of(ctx, schema, value)
|
|
204
|
-
elif key == "minLength" and 0 < value < BUFFER_SIZE
|
|
204
|
+
elif key == "minLength" and 0 < value < BUFFER_SIZE:
|
|
205
205
|
with suppress(InvalidArgument):
|
|
206
206
|
yield NegativeValue(
|
|
207
207
|
ctx.generate_from_schema({**schema, "minLength": value - 1, "maxLength": value - 1})
|
|
208
208
|
)
|
|
209
|
-
elif key == "maxLength" and value < BUFFER_SIZE
|
|
209
|
+
elif key == "maxLength" and value < BUFFER_SIZE:
|
|
210
210
|
with suppress(InvalidArgument):
|
|
211
211
|
yield NegativeValue(
|
|
212
212
|
ctx.generate_from_schema({**schema, "minLength": value + 1, "maxLength": value + 1})
|
|
@@ -238,6 +238,8 @@ def _get_properties(schema: dict | bool) -> dict | bool:
|
|
|
238
238
|
if isinstance(schema, dict):
|
|
239
239
|
if "example" in schema:
|
|
240
240
|
return {"const": schema["example"]}
|
|
241
|
+
if "examples" in schema and schema["examples"]:
|
|
242
|
+
return {"enum": schema["examples"]}
|
|
241
243
|
if schema.get("type") == "object":
|
|
242
244
|
return _get_template_schema(schema, "object")
|
|
243
245
|
return schema
|
|
@@ -261,16 +263,21 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
261
263
|
# Boundary and near boundary values
|
|
262
264
|
min_length = schema.get("minLength")
|
|
263
265
|
max_length = schema.get("maxLength")
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
266
|
+
example = schema.get("example")
|
|
267
|
+
examples = schema.get("examples")
|
|
268
|
+
if example or examples:
|
|
269
|
+
if example:
|
|
270
|
+
yield PositiveValue(example)
|
|
271
|
+
if examples:
|
|
272
|
+
for example in examples:
|
|
273
|
+
yield PositiveValue(example)
|
|
267
274
|
elif not min_length and not max_length:
|
|
268
275
|
# Default positive value
|
|
269
276
|
yield PositiveValue(ctx.generate_from_schema(schema))
|
|
270
277
|
|
|
271
278
|
seen = set()
|
|
272
279
|
|
|
273
|
-
if min_length is not None and min_length < BUFFER_SIZE
|
|
280
|
+
if min_length is not None and min_length < BUFFER_SIZE:
|
|
274
281
|
# Exactly the minimum length
|
|
275
282
|
yield PositiveValue(ctx.generate_from_schema({**schema, "maxLength": min_length}))
|
|
276
283
|
seen.add(min_length)
|
|
@@ -281,7 +288,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
281
288
|
yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger}))
|
|
282
289
|
seen.add(larger)
|
|
283
290
|
|
|
284
|
-
if max_length is not None
|
|
291
|
+
if max_length is not None:
|
|
285
292
|
# Exactly the maximum length
|
|
286
293
|
if max_length < BUFFER_SIZE and max_length not in seen:
|
|
287
294
|
yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": max_length}))
|
|
@@ -318,9 +325,15 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
318
325
|
if exclusive_maximum is not None:
|
|
319
326
|
maximum = exclusive_maximum - 1
|
|
320
327
|
multiple_of = schema.get("multipleOf")
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
328
|
+
example = schema.get("example")
|
|
329
|
+
examples = schema.get("examples")
|
|
330
|
+
|
|
331
|
+
if example or examples:
|
|
332
|
+
if example:
|
|
333
|
+
yield PositiveValue(example)
|
|
334
|
+
if examples:
|
|
335
|
+
for example in examples:
|
|
336
|
+
yield PositiveValue(example)
|
|
324
337
|
elif not minimum and not maximum:
|
|
325
338
|
# Default positive value
|
|
326
339
|
yield PositiveValue(ctx.generate_from_schema(schema))
|
|
@@ -367,8 +380,15 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
|
367
380
|
|
|
368
381
|
def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Generator[GeneratedValue, None, None]:
|
|
369
382
|
seen = set()
|
|
370
|
-
|
|
371
|
-
|
|
383
|
+
example = schema.get("example")
|
|
384
|
+
examples = schema.get("examples")
|
|
385
|
+
|
|
386
|
+
if example or examples:
|
|
387
|
+
if example:
|
|
388
|
+
yield PositiveValue(example)
|
|
389
|
+
if examples:
|
|
390
|
+
for example in examples:
|
|
391
|
+
yield PositiveValue(example)
|
|
372
392
|
else:
|
|
373
393
|
yield PositiveValue(template)
|
|
374
394
|
seen.add(len(template))
|
|
@@ -403,8 +423,15 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
|
|
|
403
423
|
|
|
404
424
|
|
|
405
425
|
def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Generator[GeneratedValue, None, None]:
|
|
406
|
-
|
|
407
|
-
|
|
426
|
+
example = schema.get("example")
|
|
427
|
+
examples = schema.get("examples")
|
|
428
|
+
|
|
429
|
+
if example or examples:
|
|
430
|
+
if example:
|
|
431
|
+
yield PositiveValue(example)
|
|
432
|
+
if examples:
|
|
433
|
+
for example in examples:
|
|
434
|
+
yield PositiveValue(example)
|
|
408
435
|
else:
|
|
409
436
|
yield PositiveValue(template)
|
|
410
437
|
# Only required properties
|
schemathesis/models.py
CHANGED
|
@@ -71,6 +71,14 @@ if TYPE_CHECKING:
|
|
|
71
71
|
from .transports.responses import GenericResponse, WSGIResponse
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
@dataclass
|
|
75
|
+
class TransitionId:
|
|
76
|
+
name: str
|
|
77
|
+
status_code: str
|
|
78
|
+
|
|
79
|
+
__slots__ = ("name", "status_code")
|
|
80
|
+
|
|
81
|
+
|
|
74
82
|
@dataclass
|
|
75
83
|
class CaseSource:
|
|
76
84
|
"""Data sources, used to generate a test case."""
|
|
@@ -79,6 +87,7 @@ class CaseSource:
|
|
|
79
87
|
response: GenericResponse
|
|
80
88
|
elapsed: float
|
|
81
89
|
overrides_all_parameters: bool
|
|
90
|
+
transition_id: TransitionId
|
|
82
91
|
|
|
83
92
|
def partial_deepcopy(self) -> CaseSource:
|
|
84
93
|
return self.__class__(
|
|
@@ -86,6 +95,7 @@ class CaseSource:
|
|
|
86
95
|
response=self.response,
|
|
87
96
|
elapsed=self.elapsed,
|
|
88
97
|
overrides_all_parameters=self.overrides_all_parameters,
|
|
98
|
+
transition_id=self.transition_id,
|
|
89
99
|
)
|
|
90
100
|
|
|
91
101
|
|
|
@@ -214,9 +224,20 @@ class Case:
|
|
|
214
224
|
def app(self) -> Any:
|
|
215
225
|
return self.operation.app
|
|
216
226
|
|
|
217
|
-
def set_source(
|
|
227
|
+
def set_source(
|
|
228
|
+
self,
|
|
229
|
+
response: GenericResponse,
|
|
230
|
+
case: Case,
|
|
231
|
+
elapsed: float,
|
|
232
|
+
overrides_all_parameters: bool,
|
|
233
|
+
transition_id: TransitionId,
|
|
234
|
+
) -> None:
|
|
218
235
|
self.source = CaseSource(
|
|
219
|
-
case=case,
|
|
236
|
+
case=case,
|
|
237
|
+
response=response,
|
|
238
|
+
elapsed=elapsed,
|
|
239
|
+
overrides_all_parameters=overrides_all_parameters,
|
|
240
|
+
transition_id=transition_id,
|
|
220
241
|
)
|
|
221
242
|
|
|
222
243
|
@property
|
schemathesis/runner/events.py
CHANGED
|
@@ -49,6 +49,8 @@ class Initialized(ExecutionEvent):
|
|
|
49
49
|
seed: int | None
|
|
50
50
|
# The base URL against which the tests are running
|
|
51
51
|
base_url: str
|
|
52
|
+
# The base path part of every operation
|
|
53
|
+
base_path: str
|
|
52
54
|
# API schema specification name
|
|
53
55
|
specification_name: str
|
|
54
56
|
# Monotonic clock value when the test run started. Used to properly calculate run duration, since this clock
|
|
@@ -77,6 +79,7 @@ class Initialized(ExecutionEvent):
|
|
|
77
79
|
links_count=schema.links_count if count_links else None,
|
|
78
80
|
location=schema.location,
|
|
79
81
|
base_url=schema.get_base_url(),
|
|
82
|
+
base_path=schema.base_path,
|
|
80
83
|
start_time=start_time or time.monotonic(),
|
|
81
84
|
started_at=started_at or current_datetime(),
|
|
82
85
|
specification_name=schema.verbose_name,
|
schemathesis/runner/impl/core.py
CHANGED
|
@@ -306,13 +306,12 @@ class BaseRunner:
|
|
|
306
306
|
if isinstance(stateful_event, stateful_events.SuiteFinished):
|
|
307
307
|
if stateful_event.failures and status != Status.error:
|
|
308
308
|
status = Status.failure
|
|
309
|
-
for failure in stateful_event.failures:
|
|
310
|
-
result.checks.append(failure)
|
|
311
309
|
elif isinstance(stateful_event, stateful_events.RunStarted):
|
|
312
310
|
test_start_time = stateful_event.timestamp
|
|
313
311
|
elif isinstance(stateful_event, stateful_events.RunFinished):
|
|
314
312
|
test_elapsed_time = stateful_event.timestamp - cast(float, test_start_time)
|
|
315
313
|
elif isinstance(stateful_event, stateful_events.StepFinished):
|
|
314
|
+
result.checks.extend(stateful_event.checks)
|
|
316
315
|
on_step_finished(stateful_event)
|
|
317
316
|
elif isinstance(stateful_event, stateful_events.Errored):
|
|
318
317
|
status = Status.error
|
|
@@ -28,7 +28,7 @@ from ..exceptions import (
|
|
|
28
28
|
make_unique_by_key,
|
|
29
29
|
)
|
|
30
30
|
from ..generation import DataGenerationMethod
|
|
31
|
-
from ..models import Case, Check, Interaction, Request, Response, Status, TestPhase, TestResult
|
|
31
|
+
from ..models import Case, Check, Interaction, Request, Response, Status, TestPhase, TestResult, TransitionId
|
|
32
32
|
from ..transports import deserialize_payload, serialize_payload
|
|
33
33
|
|
|
34
34
|
if TYPE_CHECKING:
|
|
@@ -52,7 +52,9 @@ class SerializedCase:
|
|
|
52
52
|
method: str
|
|
53
53
|
url: str
|
|
54
54
|
path_template: str
|
|
55
|
+
full_path: str
|
|
55
56
|
verbose_name: str
|
|
57
|
+
transition_id: TransitionId | None
|
|
56
58
|
# Transport info
|
|
57
59
|
verify: bool
|
|
58
60
|
# Headers coming from sources outside data generation
|
|
@@ -78,7 +80,9 @@ class SerializedCase:
|
|
|
78
80
|
method=case.method,
|
|
79
81
|
url=request_data.url,
|
|
80
82
|
path_template=case.path,
|
|
83
|
+
full_path=case.full_path,
|
|
81
84
|
verbose_name=case.operation.verbose_name,
|
|
85
|
+
transition_id=case.source.transition_id if case.source is not None else None,
|
|
82
86
|
verify=verify,
|
|
83
87
|
extra_headers=request_data.headers,
|
|
84
88
|
)
|
|
@@ -174,7 +178,11 @@ class SerializedCheck:
|
|
|
174
178
|
|
|
175
179
|
|
|
176
180
|
def _get_headers(headers: dict[str, Any] | CaseInsensitiveDict) -> dict[str, str]:
|
|
177
|
-
return {
|
|
181
|
+
return {
|
|
182
|
+
key: value[0] if isinstance(value, list) else value
|
|
183
|
+
for key, value in headers.items()
|
|
184
|
+
if key not in get_excluded_headers()
|
|
185
|
+
}
|
|
178
186
|
|
|
179
187
|
|
|
180
188
|
@dataclass
|
|
@@ -14,7 +14,7 @@ from jsonschema import RefResolver
|
|
|
14
14
|
|
|
15
15
|
from ...constants import NOT_SET
|
|
16
16
|
from ...internal.copy import fast_deepcopy
|
|
17
|
-
from ...models import APIOperation, Case
|
|
17
|
+
from ...models import APIOperation, Case, TransitionId
|
|
18
18
|
from ...parameters import ParameterSet
|
|
19
19
|
from ...stateful import ParsedData, StatefulTest, UnresolvableLink
|
|
20
20
|
from ...stateful.state_machine import Direction
|
|
@@ -226,7 +226,16 @@ class OpenAPILink(Direction):
|
|
|
226
226
|
if parameter.name not in overrides.get(parameter.location, []):
|
|
227
227
|
overrides_all_parameters = False
|
|
228
228
|
break
|
|
229
|
-
case.set_source(
|
|
229
|
+
case.set_source(
|
|
230
|
+
context.response,
|
|
231
|
+
context.case,
|
|
232
|
+
elapsed,
|
|
233
|
+
overrides_all_parameters,
|
|
234
|
+
transition_id=TransitionId(
|
|
235
|
+
name=self.name,
|
|
236
|
+
status_code=self.status_code,
|
|
237
|
+
),
|
|
238
|
+
)
|
|
230
239
|
|
|
231
240
|
def set_parameters(
|
|
232
241
|
self, case: Case, context: expressions.ExpressionContext
|
|
@@ -48,11 +48,20 @@ class OpenAPIParameter(Parameter):
|
|
|
48
48
|
|
|
49
49
|
@property
|
|
50
50
|
def is_header(self) -> bool:
|
|
51
|
-
|
|
51
|
+
return self.location in ("header", "cookie")
|
|
52
52
|
|
|
53
53
|
def as_json_schema(self, operation: APIOperation) -> dict[str, Any]:
|
|
54
54
|
"""Convert parameter's definition to JSON Schema."""
|
|
55
|
+
# JSON Schema allows `examples` as an array
|
|
56
|
+
if self.examples_field in self.definition:
|
|
57
|
+
examples = [
|
|
58
|
+
example["value"] for example in self.definition[self.examples_field].values() if "value" in example
|
|
59
|
+
]
|
|
60
|
+
else:
|
|
61
|
+
examples = None
|
|
55
62
|
schema = self.from_open_api_to_json_schema(operation, self.definition)
|
|
63
|
+
if examples is not None:
|
|
64
|
+
schema["examples"] = examples
|
|
56
65
|
return self.transform_keywords(schema)
|
|
57
66
|
|
|
58
67
|
def transform_keywords(self, schema: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -118,12 +127,9 @@ class OpenAPI20Parameter(OpenAPIParameter):
|
|
|
118
127
|
"enum",
|
|
119
128
|
"multipleOf",
|
|
120
129
|
"example",
|
|
130
|
+
"examples",
|
|
121
131
|
)
|
|
122
132
|
|
|
123
|
-
@property
|
|
124
|
-
def is_header(self) -> bool:
|
|
125
|
-
return self.location == "header"
|
|
126
|
-
|
|
127
133
|
|
|
128
134
|
@dataclass(eq=False)
|
|
129
135
|
class OpenAPI30Parameter(OpenAPIParameter):
|
|
@@ -165,12 +171,9 @@ class OpenAPI30Parameter(OpenAPIParameter):
|
|
|
165
171
|
"additionalProperties",
|
|
166
172
|
"format",
|
|
167
173
|
"example",
|
|
174
|
+
"examples",
|
|
168
175
|
)
|
|
169
176
|
|
|
170
|
-
@property
|
|
171
|
-
def is_header(self) -> bool:
|
|
172
|
-
return self.location in ("header", "cookie")
|
|
173
|
-
|
|
174
177
|
def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema: dict[str, Any]) -> dict[str, Any]:
|
|
175
178
|
open_api_schema = get_parameter_schema(operation, open_api_schema)
|
|
176
179
|
return super().from_open_api_to_json_schema(operation, open_api_schema)
|
|
@@ -219,6 +222,8 @@ class OpenAPI20Body(OpenAPIBody, OpenAPI20Parameter):
|
|
|
219
222
|
"allOf",
|
|
220
223
|
"properties",
|
|
221
224
|
"additionalProperties",
|
|
225
|
+
"example",
|
|
226
|
+
"examples",
|
|
222
227
|
)
|
|
223
228
|
# NOTE. For Open API 2.0 bodies, we still give `x-example` precedence over the schema-level `example` field to keep
|
|
224
229
|
# the precedence rules consistent.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: schemathesis
|
|
3
|
-
Version: 3.35.
|
|
3
|
+
Version: 3.35.3
|
|
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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
schemathesis/__init__.py,sha256=pNaTfaC3NSdediNQuH9QAcuIx3U-MmSydvhS65FZrxw,1984
|
|
2
2
|
schemathesis/_compat.py,sha256=y4RZd59i2NCnZ91VQhnKeMn_8t3SgvLOk2Xm8nymUHY,1837
|
|
3
3
|
schemathesis/_dependency_versions.py,sha256=pjEkkGAfOQJYNb-9UOo84V8nj_lKHr_TGDVdFwY2UU0,816
|
|
4
|
-
schemathesis/_hypothesis.py,sha256=
|
|
4
|
+
schemathesis/_hypothesis.py,sha256=bYP1KHElFIqmdlLjHOKHBXpJ1pfUPl9rfBpOt3AKQmA,14012
|
|
5
5
|
schemathesis/_lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,470
|
|
6
6
|
schemathesis/_override.py,sha256=3CbA7P9Q89W3ymaYxiOV5Xpv1yhoBqroLK4YRpYMjX4,1630
|
|
7
7
|
schemathesis/_rate_limiter.py,sha256=q_XWst5hzuAyXQRiZc4s_bx7-JlPYZM_yKDmeavt3oo,242
|
|
@@ -17,7 +17,7 @@ schemathesis/graphql.py,sha256=YkoKWY5K8lxp7H3ikAs-IsoDbiPwJvChG7O8p3DgwtI,229
|
|
|
17
17
|
schemathesis/hooks.py,sha256=Uv9rZHqM2bpb_uYBjf4kqsMeu7XdLOHpRWSaN43xIgw,14774
|
|
18
18
|
schemathesis/lazy.py,sha256=hGwSuWe5tDaGpjZTV4Mj8zqdrHDYHxR22N2p5h2yh1g,18897
|
|
19
19
|
schemathesis/loaders.py,sha256=OtCD1o0TVmSNAUF7dgHpouoAXtY6w9vEtsRVGv4lE0g,4588
|
|
20
|
-
schemathesis/models.py,sha256=
|
|
20
|
+
schemathesis/models.py,sha256=GEPDi6BQiJpcmCvSQ4L6_AJwaRC_hV5EffXM_pzzMrs,44949
|
|
21
21
|
schemathesis/parameters.py,sha256=PndmqQRlEYsCt1kWjSShPsFf6vj7X_7FRdz_-A95eNg,2258
|
|
22
22
|
schemathesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
schemathesis/sanitization.py,sha256=_qSt04f_XcHrgguyUnowvdfj-b6u409Ubu07i0ivQUQ,9011
|
|
@@ -27,7 +27,7 @@ schemathesis/targets.py,sha256=XIGRghvEzbmEJjse9aZgNEj67L3jAbiazm2rxURWgDE,2351
|
|
|
27
27
|
schemathesis/throttling.py,sha256=aisUc4MJDGIOGUAs9L2DlWWpdd4KyAFuNVKhYoaUC9M,1719
|
|
28
28
|
schemathesis/types.py,sha256=Tem2Q_zyMCd0Clp5iGKv6Fu13wdcbxVE8tCVH9WWt7A,1065
|
|
29
29
|
schemathesis/utils.py,sha256=bYvB3l1iMxiUNHu7_1qhOj5gJf_8QUssL4Uoqgrux9A,4878
|
|
30
|
-
schemathesis/cli/__init__.py,sha256=
|
|
30
|
+
schemathesis/cli/__init__.py,sha256=JT7qNr2LmAX1ceinVkMMxOFQ2DKEVpwiShQyy5zOwCo,73269
|
|
31
31
|
schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
|
|
32
32
|
schemathesis/cli/callbacks.py,sha256=PJs64n6qGrGC5Yv_yl3fGm797cvN6pp2enFLmSljGKs,15127
|
|
33
33
|
schemathesis/cli/cassettes.py,sha256=KyODNf0r1ieLe3A-CGcU2EEkQYr0fgVut27AOl54MOM,18615
|
|
@@ -60,7 +60,7 @@ schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZE
|
|
|
60
60
|
schemathesis/generation/__init__.py,sha256=IzldWIswXBjCUaVInvXDoaXIrUbtZIU5cisnWcg2IX8,1609
|
|
61
61
|
schemathesis/generation/_hypothesis.py,sha256=Qel0mBsZV6tOEspRGfbJKFZevaMgHJjzY1F0Oo1bP_Y,1408
|
|
62
62
|
schemathesis/generation/_methods.py,sha256=jCK09f4sedDfePrS-6BIiE-CcEE8fJ4ZHxq1BHoTltQ,1101
|
|
63
|
-
schemathesis/generation/coverage.py,sha256=
|
|
63
|
+
schemathesis/generation/coverage.py,sha256=WKZi_q4V_foejmDZg38N6cj_nfB_ahbh4wxYDmMVbeY,20889
|
|
64
64
|
schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
|
|
65
65
|
schemathesis/internal/copy.py,sha256=DcL56z-d69kKR_5u8mlHvjSL1UTyUKNMAwexrwHFY1s,1031
|
|
66
66
|
schemathesis/internal/datetime.py,sha256=zPLBL0XXLNfP-KYel3H2m8pnsxjsA_4d-zTOhJg2EPQ,136
|
|
@@ -72,11 +72,11 @@ schemathesis/internal/result.py,sha256=d449YvyONjqjDs-A5DAPgtAI96iT753K8sU6_1HLo
|
|
|
72
72
|
schemathesis/internal/transformation.py,sha256=M5LA4pFZC4nD_0iGfih1wLF3_q8xJas94Uuaymt-7Cw,690
|
|
73
73
|
schemathesis/internal/validation.py,sha256=G7i8jIMUpAeOnDsDF_eWYvRZe_yMprRswx0QAtMPyEw,966
|
|
74
74
|
schemathesis/runner/__init__.py,sha256=-aedUaRBCiTiaooC0OsBbdi4XP8APFOpj6eOzwt5nQ8,21366
|
|
75
|
-
schemathesis/runner/events.py,sha256=
|
|
75
|
+
schemathesis/runner/events.py,sha256=l9OmbJR1TOpUAh1okiIRWAeFBiJz5JFIX4E1cfYBo-0,11730
|
|
76
76
|
schemathesis/runner/probes.py,sha256=no5AfO3kse25qvHevjeUfB0Q3C860V2AYzschUW3QMQ,5688
|
|
77
|
-
schemathesis/runner/serialization.py,sha256=
|
|
77
|
+
schemathesis/runner/serialization.py,sha256=p7jC8QRFcwU9h9rvc6a-1Wr8bOVQ3O4HyWJYXXvkjNU,20384
|
|
78
78
|
schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
|
|
79
|
-
schemathesis/runner/impl/core.py,sha256=
|
|
79
|
+
schemathesis/runner/impl/core.py,sha256=Fc0OZm9uAGyGYWkBcXNm2sL4wITntccZtz592g3z_88,45555
|
|
80
80
|
schemathesis/runner/impl/solo.py,sha256=N7-pUL6nWGiSRUC4Zqy1T4h99vbeQowP6b6cMnobOow,3042
|
|
81
81
|
schemathesis/runner/impl/threadpool.py,sha256=JtTn5X7gqpbkO4q8n5kykGEPQCpJ9hXJrvU3KMF1CXA,14948
|
|
82
82
|
schemathesis/service/__init__.py,sha256=cDVTCFD1G-vvhxZkJUwiToTAEQ-0ByIoqwXvJBCf_V8,472
|
|
@@ -109,10 +109,10 @@ schemathesis/specs/openapi/converter.py,sha256=TaYgc5BBHPdkN-n0lqpbeVgLu3eL3L8Wu
|
|
|
109
109
|
schemathesis/specs/openapi/definitions.py,sha256=Z186F0gNBSCmPg-Kk7Q-n6XxEZHIOzgUyeqixlC62XE,94058
|
|
110
110
|
schemathesis/specs/openapi/examples.py,sha256=_6vqwVfGuPaJ9GTqmlk6siiS0pikk5wxelLsFQJlaEc,16283
|
|
111
111
|
schemathesis/specs/openapi/formats.py,sha256=JmmkQWNAj5XreXb7Edgj4LADAf4m86YulR_Ec8evpJ4,1220
|
|
112
|
-
schemathesis/specs/openapi/links.py,sha256=
|
|
112
|
+
schemathesis/specs/openapi/links.py,sha256=RvWJZqHfOeoNjhARBX9GSjG6mbQBwhMfB4-iEEQ9_TI,17737
|
|
113
113
|
schemathesis/specs/openapi/loaders.py,sha256=MxzMoCufll_a_QFjQ9khBcM2dAD3cw8Fn6yCtpX_BzE,25628
|
|
114
114
|
schemathesis/specs/openapi/media_types.py,sha256=dNTxpRQbY3SubdVjh4Cjb38R6Bc9MF9BsRQwPD87x0g,1017
|
|
115
|
-
schemathesis/specs/openapi/parameters.py,sha256=
|
|
115
|
+
schemathesis/specs/openapi/parameters.py,sha256=UkTjSa4PE9nEBTljbRrwTcoKHYN8PFK7hBoR5qD9k6g,13932
|
|
116
116
|
schemathesis/specs/openapi/references.py,sha256=euxM02kQGMHh4Ss1jWjOY_gyw_HazafKITIsvOEiAvI,9831
|
|
117
117
|
schemathesis/specs/openapi/schemas.py,sha256=3pZEAU9sB9PT32z3BhJPsSSR0wVpJXL20-z9hMWS52s,53053
|
|
118
118
|
schemathesis/specs/openapi/security.py,sha256=nEhDB_SvEFldmfpa9uOQywfWN6DtXHKmgtwucJvfN5Q,7096
|
|
@@ -147,8 +147,8 @@ schemathesis/transports/auth.py,sha256=yELjkEkfx4g74hNrd0Db9aFf0xDJDRIwhg2vzKOTZ
|
|
|
147
147
|
schemathesis/transports/content_types.py,sha256=VrcRQvF5T_TUjrCyrZcYF2LOwKfs3IrLcMtkVSp1ImI,2189
|
|
148
148
|
schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
|
|
149
149
|
schemathesis/transports/responses.py,sha256=6-gvVcRK0Ho_lSydUysBNFWoJwZEiEgf6Iv-GWkQGd8,1675
|
|
150
|
-
schemathesis-3.35.
|
|
151
|
-
schemathesis-3.35.
|
|
152
|
-
schemathesis-3.35.
|
|
153
|
-
schemathesis-3.35.
|
|
154
|
-
schemathesis-3.35.
|
|
150
|
+
schemathesis-3.35.3.dist-info/METADATA,sha256=mvR0yYu9CyG09IvD4Dk8DBQ4vlLXKanpRpqshLdbgSQ,12856
|
|
151
|
+
schemathesis-3.35.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
152
|
+
schemathesis-3.35.3.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
|
|
153
|
+
schemathesis-3.35.3.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
|
|
154
|
+
schemathesis-3.35.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|