schemathesis 3.25.6__py3-none-any.whl → 3.39.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- schemathesis/__init__.py +6 -6
- schemathesis/_compat.py +2 -2
- schemathesis/_dependency_versions.py +4 -2
- schemathesis/_hypothesis.py +369 -56
- schemathesis/_lazy_import.py +1 -0
- schemathesis/_override.py +5 -4
- schemathesis/_patches.py +21 -0
- schemathesis/_rate_limiter.py +7 -0
- schemathesis/_xml.py +75 -22
- schemathesis/auths.py +78 -16
- schemathesis/checks.py +21 -9
- schemathesis/cli/__init__.py +783 -432
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/callbacks.py +58 -13
- schemathesis/cli/cassettes.py +233 -47
- schemathesis/cli/constants.py +8 -2
- schemathesis/cli/context.py +22 -5
- schemathesis/cli/debug.py +2 -1
- schemathesis/cli/handlers.py +4 -1
- schemathesis/cli/junitxml.py +103 -22
- schemathesis/cli/options.py +15 -4
- schemathesis/cli/output/default.py +258 -112
- schemathesis/cli/output/short.py +23 -8
- schemathesis/cli/reporting.py +79 -0
- schemathesis/cli/sanitization.py +6 -0
- schemathesis/code_samples.py +5 -3
- schemathesis/constants.py +1 -0
- schemathesis/contrib/openapi/__init__.py +1 -1
- schemathesis/contrib/openapi/fill_missing_examples.py +3 -1
- schemathesis/contrib/openapi/formats/uuid.py +2 -1
- schemathesis/contrib/unique_data.py +3 -3
- schemathesis/exceptions.py +76 -65
- schemathesis/experimental/__init__.py +35 -0
- schemathesis/extra/_aiohttp.py +1 -0
- schemathesis/extra/_flask.py +4 -1
- schemathesis/extra/_server.py +1 -0
- schemathesis/extra/pytest_plugin.py +17 -25
- schemathesis/failures.py +77 -9
- schemathesis/filters.py +185 -8
- schemathesis/fixups/__init__.py +1 -0
- schemathesis/fixups/fast_api.py +2 -2
- schemathesis/fixups/utf8_bom.py +1 -2
- schemathesis/generation/__init__.py +20 -36
- schemathesis/generation/_hypothesis.py +59 -0
- schemathesis/generation/_methods.py +44 -0
- schemathesis/generation/coverage.py +931 -0
- schemathesis/graphql.py +0 -1
- schemathesis/hooks.py +89 -12
- schemathesis/internal/checks.py +84 -0
- schemathesis/internal/copy.py +22 -3
- schemathesis/internal/deprecation.py +6 -2
- schemathesis/internal/diff.py +15 -0
- schemathesis/internal/extensions.py +27 -0
- schemathesis/internal/jsonschema.py +2 -1
- schemathesis/internal/output.py +68 -0
- schemathesis/internal/result.py +1 -1
- schemathesis/internal/transformation.py +11 -0
- schemathesis/lazy.py +138 -25
- schemathesis/loaders.py +7 -5
- schemathesis/models.py +318 -211
- schemathesis/parameters.py +4 -0
- schemathesis/runner/__init__.py +50 -15
- schemathesis/runner/events.py +65 -5
- schemathesis/runner/impl/context.py +104 -0
- schemathesis/runner/impl/core.py +388 -177
- schemathesis/runner/impl/solo.py +19 -29
- schemathesis/runner/impl/threadpool.py +70 -79
- schemathesis/runner/probes.py +11 -9
- schemathesis/runner/serialization.py +150 -17
- schemathesis/sanitization.py +5 -1
- schemathesis/schemas.py +170 -102
- schemathesis/serializers.py +7 -2
- schemathesis/service/ci.py +1 -0
- schemathesis/service/client.py +39 -6
- schemathesis/service/events.py +5 -1
- schemathesis/service/extensions.py +224 -0
- schemathesis/service/hosts.py +6 -2
- schemathesis/service/metadata.py +25 -0
- schemathesis/service/models.py +211 -2
- schemathesis/service/report.py +6 -6
- schemathesis/service/serialization.py +45 -71
- schemathesis/service/usage.py +1 -0
- schemathesis/specs/graphql/_cache.py +26 -0
- schemathesis/specs/graphql/loaders.py +25 -5
- schemathesis/specs/graphql/nodes.py +1 -0
- schemathesis/specs/graphql/scalars.py +2 -2
- schemathesis/specs/graphql/schemas.py +130 -100
- schemathesis/specs/graphql/validation.py +1 -2
- schemathesis/specs/openapi/__init__.py +1 -0
- schemathesis/specs/openapi/_cache.py +123 -0
- schemathesis/specs/openapi/_hypothesis.py +78 -60
- schemathesis/specs/openapi/checks.py +504 -25
- schemathesis/specs/openapi/converter.py +31 -4
- schemathesis/specs/openapi/definitions.py +10 -17
- schemathesis/specs/openapi/examples.py +126 -12
- schemathesis/specs/openapi/expressions/__init__.py +37 -2
- schemathesis/specs/openapi/expressions/context.py +1 -1
- schemathesis/specs/openapi/expressions/extractors.py +26 -0
- schemathesis/specs/openapi/expressions/lexer.py +20 -18
- schemathesis/specs/openapi/expressions/nodes.py +29 -6
- schemathesis/specs/openapi/expressions/parser.py +26 -5
- schemathesis/specs/openapi/formats.py +44 -0
- schemathesis/specs/openapi/links.py +125 -42
- schemathesis/specs/openapi/loaders.py +77 -36
- schemathesis/specs/openapi/media_types.py +34 -0
- schemathesis/specs/openapi/negative/__init__.py +6 -3
- schemathesis/specs/openapi/negative/mutations.py +21 -6
- schemathesis/specs/openapi/parameters.py +39 -25
- schemathesis/specs/openapi/patterns.py +137 -0
- schemathesis/specs/openapi/references.py +37 -7
- schemathesis/specs/openapi/schemas.py +360 -241
- schemathesis/specs/openapi/security.py +25 -7
- schemathesis/specs/openapi/serialization.py +1 -0
- schemathesis/specs/openapi/stateful/__init__.py +198 -70
- schemathesis/specs/openapi/stateful/statistic.py +198 -0
- schemathesis/specs/openapi/stateful/types.py +14 -0
- schemathesis/specs/openapi/utils.py +6 -1
- schemathesis/specs/openapi/validation.py +1 -0
- schemathesis/stateful/__init__.py +35 -21
- schemathesis/stateful/config.py +97 -0
- schemathesis/stateful/context.py +135 -0
- schemathesis/stateful/events.py +274 -0
- schemathesis/stateful/runner.py +309 -0
- schemathesis/stateful/sink.py +68 -0
- schemathesis/stateful/state_machine.py +67 -38
- schemathesis/stateful/statistic.py +22 -0
- schemathesis/stateful/validation.py +100 -0
- schemathesis/targets.py +33 -1
- schemathesis/throttling.py +25 -5
- schemathesis/transports/__init__.py +354 -0
- schemathesis/transports/asgi.py +7 -0
- schemathesis/transports/auth.py +25 -2
- schemathesis/transports/content_types.py +3 -1
- schemathesis/transports/headers.py +2 -1
- schemathesis/transports/responses.py +9 -4
- schemathesis/types.py +9 -0
- schemathesis/utils.py +11 -16
- schemathesis-3.39.7.dist-info/METADATA +293 -0
- schemathesis-3.39.7.dist-info/RECORD +160 -0
- {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/WHEEL +1 -1
- schemathesis/specs/openapi/filters.py +0 -49
- schemathesis/specs/openapi/stateful/links.py +0 -92
- schemathesis-3.25.6.dist-info/METADATA +0 -356
- schemathesis-3.25.6.dist-info/RECORD +0 -134
- {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/licenses/LICENSE +0 -0
schemathesis/parameters.py
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
These are basic entities that describe what data could be sent to the API.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
7
|
+
|
|
6
8
|
from dataclasses import dataclass, field
|
|
7
9
|
from typing import TYPE_CHECKING, Any, Generator, Generic, TypeVar
|
|
8
10
|
|
|
@@ -53,6 +55,8 @@ class ParameterSet(Generic[P]):
|
|
|
53
55
|
|
|
54
56
|
items: list[P] = field(default_factory=list)
|
|
55
57
|
|
|
58
|
+
def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
|
|
59
|
+
|
|
56
60
|
def add(self, parameter: P) -> None:
|
|
57
61
|
"""Add a new parameter."""
|
|
58
62
|
self.items.append(parameter)
|
schemathesis/runner/__init__.py
CHANGED
|
@@ -4,7 +4,6 @@ from random import Random
|
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable
|
|
5
5
|
from urllib.parse import urlparse
|
|
6
6
|
|
|
7
|
-
from .._override import CaseOverride
|
|
8
7
|
from ..constants import (
|
|
9
8
|
DEFAULT_DEADLINE,
|
|
10
9
|
DEFAULT_STATEFUL_RECURSION_LIMIT,
|
|
@@ -12,6 +11,7 @@ from ..constants import (
|
|
|
12
11
|
)
|
|
13
12
|
from ..exceptions import SchemaError
|
|
14
13
|
from ..generation import DEFAULT_DATA_GENERATION_METHODS, DataGenerationMethod, GenerationConfig
|
|
14
|
+
from ..internal.checks import CheckConfig
|
|
15
15
|
from ..internal.datetime import current_datetime
|
|
16
16
|
from ..internal.deprecation import deprecated_function
|
|
17
17
|
from ..internal.validation import file_exists
|
|
@@ -19,17 +19,20 @@ from ..loaders import load_app
|
|
|
19
19
|
from ..specs.graphql import loaders as gql_loaders
|
|
20
20
|
from ..specs.openapi import loaders as oas_loaders
|
|
21
21
|
from ..targets import DEFAULT_TARGETS, Target
|
|
22
|
+
from ..transports import RequestConfig
|
|
22
23
|
from ..transports.auth import get_requests_auth
|
|
23
24
|
from ..types import Filter, NotSet, RawAuth, RequestCert
|
|
25
|
+
from . import events
|
|
24
26
|
from .probes import ProbeConfig
|
|
25
27
|
|
|
26
28
|
if TYPE_CHECKING:
|
|
27
29
|
import hypothesis
|
|
28
30
|
|
|
31
|
+
from .._override import CaseOverride
|
|
29
32
|
from ..models import CheckFunction
|
|
30
33
|
from ..schemas import BaseSchema
|
|
34
|
+
from ..service.client import ServiceClient
|
|
31
35
|
from ..stateful import Stateful
|
|
32
|
-
from . import events
|
|
33
36
|
from .impl import BaseRunner
|
|
34
37
|
|
|
35
38
|
|
|
@@ -78,6 +81,7 @@ def prepare(
|
|
|
78
81
|
hypothesis_suppress_health_check: list[hypothesis.HealthCheck] | None = None,
|
|
79
82
|
hypothesis_verbosity: hypothesis.Verbosity | None = None,
|
|
80
83
|
probe_config: ProbeConfig | None = None,
|
|
84
|
+
service_client: ServiceClient | None = None,
|
|
81
85
|
) -> Generator[events.ExecutionEvent, None, None]:
|
|
82
86
|
"""Prepare a generator that will run test cases against the given API definition."""
|
|
83
87
|
from ..checks import DEFAULT_CHECKS
|
|
@@ -132,6 +136,7 @@ def prepare(
|
|
|
132
136
|
count_operations=count_operations,
|
|
133
137
|
count_links=count_links,
|
|
134
138
|
probe_config=probe_config,
|
|
139
|
+
service_client=service_client,
|
|
135
140
|
)
|
|
136
141
|
|
|
137
142
|
|
|
@@ -193,6 +198,7 @@ def execute_from_schema(
|
|
|
193
198
|
count_operations: bool = True,
|
|
194
199
|
count_links: bool = True,
|
|
195
200
|
probe_config: ProbeConfig | None = None,
|
|
201
|
+
service_client: ServiceClient | None,
|
|
196
202
|
) -> Generator[events.ExecutionEvent, None, None]:
|
|
197
203
|
"""Execute tests for the given schema.
|
|
198
204
|
|
|
@@ -243,6 +249,7 @@ def execute_from_schema(
|
|
|
243
249
|
count_operations=count_operations,
|
|
244
250
|
count_links=count_links,
|
|
245
251
|
probe_config=probe_config,
|
|
252
|
+
service_client=service_client,
|
|
246
253
|
).execute()
|
|
247
254
|
except SchemaError as error:
|
|
248
255
|
yield events.InternalError.from_schema_error(error)
|
|
@@ -273,7 +280,7 @@ def load_schema(
|
|
|
273
280
|
operation_id: Filter | None = None,
|
|
274
281
|
) -> BaseSchema:
|
|
275
282
|
"""Load schema via specified loader and parameters."""
|
|
276
|
-
loader_options = {
|
|
283
|
+
loader_options: dict[str, Any] = {
|
|
277
284
|
key: value
|
|
278
285
|
for key, value in (
|
|
279
286
|
("base_url", base_url),
|
|
@@ -339,8 +346,10 @@ def from_schema(
|
|
|
339
346
|
request_cert: RequestCert | None = None,
|
|
340
347
|
seed: int | None = None,
|
|
341
348
|
exit_first: bool = False,
|
|
349
|
+
no_failfast: bool = False,
|
|
342
350
|
max_failures: int | None = None,
|
|
343
351
|
started_at: str | None = None,
|
|
352
|
+
unique_data: bool = False,
|
|
344
353
|
dry_run: bool = False,
|
|
345
354
|
store_interactions: bool = False,
|
|
346
355
|
stateful: Stateful | None = None,
|
|
@@ -348,11 +357,13 @@ def from_schema(
|
|
|
348
357
|
count_operations: bool = True,
|
|
349
358
|
count_links: bool = True,
|
|
350
359
|
probe_config: ProbeConfig | None = None,
|
|
360
|
+
checks_config: CheckConfig | None = None,
|
|
361
|
+
service_client: ServiceClient | None = None,
|
|
351
362
|
) -> BaseRunner:
|
|
352
363
|
import hypothesis
|
|
353
|
-
from starlette.applications import Starlette
|
|
354
364
|
|
|
355
365
|
from ..checks import DEFAULT_CHECKS
|
|
366
|
+
from ..transports.asgi import is_asgi_app
|
|
356
367
|
from .impl import (
|
|
357
368
|
SingleThreadASGIRunner,
|
|
358
369
|
SingleThreadRunner,
|
|
@@ -363,10 +374,16 @@ def from_schema(
|
|
|
363
374
|
)
|
|
364
375
|
|
|
365
376
|
checks = checks or DEFAULT_CHECKS
|
|
377
|
+
checks_config = checks_config or CheckConfig()
|
|
366
378
|
probe_config = probe_config or ProbeConfig()
|
|
367
379
|
|
|
368
380
|
hypothesis_settings = hypothesis_settings or hypothesis.settings(deadline=DEFAULT_DEADLINE)
|
|
369
|
-
|
|
381
|
+
request_config = RequestConfig(
|
|
382
|
+
timeout=request_timeout,
|
|
383
|
+
tls_verify=request_tls_verify,
|
|
384
|
+
proxy=request_proxy,
|
|
385
|
+
cert=request_cert,
|
|
386
|
+
)
|
|
370
387
|
|
|
371
388
|
# Use the same seed for all tests unless `derandomize=True` is used
|
|
372
389
|
if seed is None and not hypothesis_settings.derandomize:
|
|
@@ -388,13 +405,12 @@ def from_schema(
|
|
|
388
405
|
headers=headers,
|
|
389
406
|
seed=seed,
|
|
390
407
|
workers_num=workers_num,
|
|
391
|
-
|
|
392
|
-
request_tls_verify=request_tls_verify,
|
|
393
|
-
request_proxy=request_proxy,
|
|
394
|
-
request_cert=request_cert,
|
|
408
|
+
request_config=request_config,
|
|
395
409
|
exit_first=exit_first,
|
|
410
|
+
no_failfast=no_failfast,
|
|
396
411
|
max_failures=max_failures,
|
|
397
412
|
started_at=started_at,
|
|
413
|
+
unique_data=unique_data,
|
|
398
414
|
dry_run=dry_run,
|
|
399
415
|
store_interactions=store_interactions,
|
|
400
416
|
stateful=stateful,
|
|
@@ -402,8 +418,10 @@ def from_schema(
|
|
|
402
418
|
count_operations=count_operations,
|
|
403
419
|
count_links=count_links,
|
|
404
420
|
probe_config=probe_config,
|
|
421
|
+
checks_config=checks_config,
|
|
422
|
+
service_client=service_client,
|
|
405
423
|
)
|
|
406
|
-
if
|
|
424
|
+
if is_asgi_app(schema.app):
|
|
407
425
|
return ThreadPoolASGIRunner(
|
|
408
426
|
schema=schema,
|
|
409
427
|
checks=checks,
|
|
@@ -417,8 +435,10 @@ def from_schema(
|
|
|
417
435
|
headers=headers,
|
|
418
436
|
seed=seed,
|
|
419
437
|
exit_first=exit_first,
|
|
438
|
+
no_failfast=no_failfast,
|
|
420
439
|
max_failures=max_failures,
|
|
421
440
|
started_at=started_at,
|
|
441
|
+
unique_data=unique_data,
|
|
422
442
|
dry_run=dry_run,
|
|
423
443
|
store_interactions=store_interactions,
|
|
424
444
|
stateful=stateful,
|
|
@@ -426,6 +446,8 @@ def from_schema(
|
|
|
426
446
|
count_operations=count_operations,
|
|
427
447
|
count_links=count_links,
|
|
428
448
|
probe_config=probe_config,
|
|
449
|
+
checks_config=checks_config,
|
|
450
|
+
service_client=service_client,
|
|
429
451
|
)
|
|
430
452
|
return ThreadPoolWSGIRunner(
|
|
431
453
|
schema=schema,
|
|
@@ -441,8 +463,10 @@ def from_schema(
|
|
|
441
463
|
seed=seed,
|
|
442
464
|
workers_num=workers_num,
|
|
443
465
|
exit_first=exit_first,
|
|
466
|
+
no_failfast=no_failfast,
|
|
444
467
|
max_failures=max_failures,
|
|
445
468
|
started_at=started_at,
|
|
469
|
+
unique_data=unique_data,
|
|
446
470
|
dry_run=dry_run,
|
|
447
471
|
store_interactions=store_interactions,
|
|
448
472
|
stateful=stateful,
|
|
@@ -450,6 +474,8 @@ def from_schema(
|
|
|
450
474
|
count_operations=count_operations,
|
|
451
475
|
count_links=count_links,
|
|
452
476
|
probe_config=probe_config,
|
|
477
|
+
checks_config=checks_config,
|
|
478
|
+
service_client=service_client,
|
|
453
479
|
)
|
|
454
480
|
if not schema.app:
|
|
455
481
|
return SingleThreadRunner(
|
|
@@ -464,13 +490,12 @@ def from_schema(
|
|
|
464
490
|
override=override,
|
|
465
491
|
headers=headers,
|
|
466
492
|
seed=seed,
|
|
467
|
-
|
|
468
|
-
request_tls_verify=request_tls_verify,
|
|
469
|
-
request_proxy=request_proxy,
|
|
470
|
-
request_cert=request_cert,
|
|
493
|
+
request_config=request_config,
|
|
471
494
|
exit_first=exit_first,
|
|
495
|
+
no_failfast=no_failfast,
|
|
472
496
|
max_failures=max_failures,
|
|
473
497
|
started_at=started_at,
|
|
498
|
+
unique_data=unique_data,
|
|
474
499
|
dry_run=dry_run,
|
|
475
500
|
store_interactions=store_interactions,
|
|
476
501
|
stateful=stateful,
|
|
@@ -478,8 +503,10 @@ def from_schema(
|
|
|
478
503
|
count_operations=count_operations,
|
|
479
504
|
count_links=count_links,
|
|
480
505
|
probe_config=probe_config,
|
|
506
|
+
checks_config=checks_config,
|
|
507
|
+
service_client=service_client,
|
|
481
508
|
)
|
|
482
|
-
if
|
|
509
|
+
if is_asgi_app(schema.app):
|
|
483
510
|
return SingleThreadASGIRunner(
|
|
484
511
|
schema=schema,
|
|
485
512
|
checks=checks,
|
|
@@ -493,8 +520,10 @@ def from_schema(
|
|
|
493
520
|
headers=headers,
|
|
494
521
|
seed=seed,
|
|
495
522
|
exit_first=exit_first,
|
|
523
|
+
no_failfast=no_failfast,
|
|
496
524
|
max_failures=max_failures,
|
|
497
525
|
started_at=started_at,
|
|
526
|
+
unique_data=unique_data,
|
|
498
527
|
dry_run=dry_run,
|
|
499
528
|
store_interactions=store_interactions,
|
|
500
529
|
stateful=stateful,
|
|
@@ -502,6 +531,8 @@ def from_schema(
|
|
|
502
531
|
count_operations=count_operations,
|
|
503
532
|
count_links=count_links,
|
|
504
533
|
probe_config=probe_config,
|
|
534
|
+
checks_config=checks_config,
|
|
535
|
+
service_client=service_client,
|
|
505
536
|
)
|
|
506
537
|
return SingleThreadWSGIRunner(
|
|
507
538
|
schema=schema,
|
|
@@ -516,8 +547,10 @@ def from_schema(
|
|
|
516
547
|
headers=headers,
|
|
517
548
|
seed=seed,
|
|
518
549
|
exit_first=exit_first,
|
|
550
|
+
no_failfast=no_failfast,
|
|
519
551
|
max_failures=max_failures,
|
|
520
552
|
started_at=started_at,
|
|
553
|
+
unique_data=unique_data,
|
|
521
554
|
dry_run=dry_run,
|
|
522
555
|
store_interactions=store_interactions,
|
|
523
556
|
stateful=stateful,
|
|
@@ -525,6 +558,8 @@ def from_schema(
|
|
|
525
558
|
count_operations=count_operations,
|
|
526
559
|
count_links=count_links,
|
|
527
560
|
probe_config=probe_config,
|
|
561
|
+
checks_config=checks_config,
|
|
562
|
+
service_client=service_client,
|
|
528
563
|
)
|
|
529
564
|
|
|
530
565
|
|
schemathesis/runner/events.py
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import enum
|
|
3
4
|
import threading
|
|
4
5
|
import time
|
|
5
6
|
from dataclasses import asdict, dataclass, field
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
7
8
|
|
|
9
|
+
from ..exceptions import RuntimeErrorType, SchemaError, SchemaErrorType, format_exception
|
|
8
10
|
from ..internal.datetime import current_datetime
|
|
9
|
-
from ..
|
|
10
|
-
from ..exceptions import SchemaError, SchemaErrorType, format_exception, RuntimeErrorType
|
|
11
|
+
from ..internal.result import Err, Ok, Result
|
|
11
12
|
from .serialization import SerializedError, SerializedTestResult
|
|
12
13
|
|
|
13
|
-
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
+
from ..generation import DataGenerationMethod
|
|
15
16
|
from ..models import APIOperation, Status, TestResult, TestResultSet
|
|
16
|
-
from ..schemas import BaseSchema
|
|
17
|
+
from ..schemas import BaseSchema, Specification
|
|
18
|
+
from ..service.models import AnalysisResult
|
|
19
|
+
from ..stateful import events
|
|
17
20
|
from . import probes
|
|
18
21
|
|
|
19
22
|
|
|
@@ -36,6 +39,7 @@ class Initialized(ExecutionEvent):
|
|
|
36
39
|
"""Runner is initialized, settings are prepared, requests session is ready."""
|
|
37
40
|
|
|
38
41
|
schema: dict[str, Any]
|
|
42
|
+
specification: Specification
|
|
39
43
|
# Total number of operations in the schema
|
|
40
44
|
operations_count: int | None
|
|
41
45
|
# Total number of links in the schema
|
|
@@ -45,6 +49,8 @@ class Initialized(ExecutionEvent):
|
|
|
45
49
|
seed: int | None
|
|
46
50
|
# The base URL against which the tests are running
|
|
47
51
|
base_url: str
|
|
52
|
+
# The base path part of every operation
|
|
53
|
+
base_path: str
|
|
48
54
|
# API schema specification name
|
|
49
55
|
specification_name: str
|
|
50
56
|
# Monotonic clock value when the test run started. Used to properly calculate run duration, since this clock
|
|
@@ -68,10 +74,12 @@ class Initialized(ExecutionEvent):
|
|
|
68
74
|
"""Computes all needed data from a schema instance."""
|
|
69
75
|
return cls(
|
|
70
76
|
schema=schema.raw_schema,
|
|
77
|
+
specification=schema.specification,
|
|
71
78
|
operations_count=schema.operations_count if count_operations else None,
|
|
72
79
|
links_count=schema.links_count if count_links else None,
|
|
73
80
|
location=schema.location,
|
|
74
81
|
base_url=schema.get_base_url(),
|
|
82
|
+
base_path=schema.base_path,
|
|
75
83
|
start_time=start_time or time.monotonic(),
|
|
76
84
|
started_at=started_at or current_datetime(),
|
|
77
85
|
specification_name=schema.verbose_name,
|
|
@@ -93,6 +101,35 @@ class AfterProbing(ExecutionEvent):
|
|
|
93
101
|
return {"probes": [probe.serialize() for probe in probes], "events_type": self.__class__.__name__}
|
|
94
102
|
|
|
95
103
|
|
|
104
|
+
@dataclass
|
|
105
|
+
class BeforeAnalysis(ExecutionEvent):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class AfterAnalysis(ExecutionEvent):
|
|
111
|
+
analysis: Result[AnalysisResult, Exception] | None
|
|
112
|
+
|
|
113
|
+
def _serialize(self) -> dict[str, Any]:
|
|
114
|
+
from ..service.models import AnalysisSuccess
|
|
115
|
+
|
|
116
|
+
data = {}
|
|
117
|
+
if isinstance(self.analysis, Ok):
|
|
118
|
+
result = self.analysis.ok()
|
|
119
|
+
if isinstance(result, AnalysisSuccess):
|
|
120
|
+
data["analysis_id"] = result.id
|
|
121
|
+
else:
|
|
122
|
+
data["error"] = result.message
|
|
123
|
+
elif isinstance(self.analysis, Err):
|
|
124
|
+
data["error"] = format_exception(self.analysis.err())
|
|
125
|
+
return data
|
|
126
|
+
|
|
127
|
+
def asdict(self, **kwargs: Any) -> dict[str, Any]:
|
|
128
|
+
data = self._serialize()
|
|
129
|
+
data["event_type"] = self.__class__.__name__
|
|
130
|
+
return data
|
|
131
|
+
|
|
132
|
+
|
|
96
133
|
class CurrentOperationMixin:
|
|
97
134
|
method: str
|
|
98
135
|
path: str
|
|
@@ -275,6 +312,29 @@ class InternalError(ExecutionEvent):
|
|
|
275
312
|
)
|
|
276
313
|
|
|
277
314
|
|
|
315
|
+
@dataclass
|
|
316
|
+
class StatefulEvent(ExecutionEvent):
|
|
317
|
+
"""Represents an event originating from the state machine runner."""
|
|
318
|
+
|
|
319
|
+
data: events.StatefulEvent
|
|
320
|
+
|
|
321
|
+
__slots__ = ("data",)
|
|
322
|
+
|
|
323
|
+
def asdict(self, **kwargs: Any) -> dict[str, Any]:
|
|
324
|
+
return {"data": self.data.asdict(**kwargs), "event_type": self.__class__.__name__}
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@dataclass
|
|
328
|
+
class AfterStatefulExecution(ExecutionEvent):
|
|
329
|
+
"""Happens after the stateful test run."""
|
|
330
|
+
|
|
331
|
+
status: Status
|
|
332
|
+
result: SerializedTestResult
|
|
333
|
+
elapsed_time: float
|
|
334
|
+
data_generation_method: list[DataGenerationMethod]
|
|
335
|
+
thread_id: int = field(default_factory=threading.get_ident)
|
|
336
|
+
|
|
337
|
+
|
|
278
338
|
@dataclass
|
|
279
339
|
class Finished(ExecutionEvent):
|
|
280
340
|
"""The final event of the run.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from ...constants import NOT_SET
|
|
7
|
+
from ...internal.checks import CheckConfig
|
|
8
|
+
from ...models import TestResult, TestResultSet
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
import threading
|
|
12
|
+
|
|
13
|
+
from ..._override import CaseOverride
|
|
14
|
+
from ...exceptions import OperationSchemaError
|
|
15
|
+
from ...models import Case
|
|
16
|
+
from ...types import NotSet, RawAuth
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class RunnerContext:
|
|
21
|
+
"""Holds context shared for a test run."""
|
|
22
|
+
|
|
23
|
+
data: TestResultSet
|
|
24
|
+
auth: RawAuth | None
|
|
25
|
+
seed: int | None
|
|
26
|
+
stop_event: threading.Event
|
|
27
|
+
unique_data: bool
|
|
28
|
+
outcome_cache: dict[int, BaseException | None]
|
|
29
|
+
checks_config: CheckConfig
|
|
30
|
+
override: CaseOverride | None
|
|
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
|
+
)
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
seed: int | None,
|
|
49
|
+
auth: RawAuth | None,
|
|
50
|
+
stop_event: threading.Event,
|
|
51
|
+
unique_data: bool,
|
|
52
|
+
checks_config: CheckConfig,
|
|
53
|
+
override: CaseOverride | None,
|
|
54
|
+
no_failfast: bool,
|
|
55
|
+
) -> None:
|
|
56
|
+
self.data = TestResultSet(seed=seed)
|
|
57
|
+
self.auth = auth
|
|
58
|
+
self.seed = seed
|
|
59
|
+
self.stop_event = stop_event
|
|
60
|
+
self.outcome_cache = {}
|
|
61
|
+
self.unique_data = unique_data
|
|
62
|
+
self.checks_config = checks_config
|
|
63
|
+
self.override = override
|
|
64
|
+
self.no_failfast = no_failfast
|
|
65
|
+
|
|
66
|
+
def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def is_stopped(self) -> bool:
|
|
70
|
+
return self.stop_event.is_set()
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def has_all_not_found(self) -> bool:
|
|
74
|
+
"""Check if all responses are 404."""
|
|
75
|
+
has_not_found = False
|
|
76
|
+
for entry in self.data.results:
|
|
77
|
+
for check in entry.checks:
|
|
78
|
+
if check.response is not None:
|
|
79
|
+
if check.response.status_code == 404:
|
|
80
|
+
has_not_found = True
|
|
81
|
+
else:
|
|
82
|
+
# There are non-404 responses, no reason to check any other response
|
|
83
|
+
return False
|
|
84
|
+
# Only happens if all responses are 404, or there are no responses at all.
|
|
85
|
+
# In the first case, it returns True, for the latter - False
|
|
86
|
+
return has_not_found
|
|
87
|
+
|
|
88
|
+
def add_result(self, result: TestResult) -> None:
|
|
89
|
+
self.data.append(result)
|
|
90
|
+
|
|
91
|
+
def add_generic_error(self, error: OperationSchemaError) -> None:
|
|
92
|
+
self.data.generic_errors.append(error)
|
|
93
|
+
|
|
94
|
+
def add_warning(self, message: str) -> None:
|
|
95
|
+
self.data.add_warning(message)
|
|
96
|
+
|
|
97
|
+
def cache_outcome(self, case: Case, outcome: BaseException | None) -> None:
|
|
98
|
+
self.outcome_cache[hash(case)] = outcome
|
|
99
|
+
|
|
100
|
+
def get_cached_outcome(self, case: Case) -> BaseException | None | NotSet:
|
|
101
|
+
return self.outcome_cache.get(hash(case), NOT_SET)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
ALL_NOT_FOUND_WARNING_MESSAGE = "All API responses have a 404 status code. Did you specify the proper API location?"
|