robotframework-openapitools 1.0.0b3__py3-none-any.whl → 1.0.0b4__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.
- OpenApiDriver/openapi_executors.py +8 -8
- OpenApiDriver/openapi_reader.py +12 -13
- OpenApiDriver/openapidriver.libspec +4 -41
- OpenApiLibCore/__init__.py +0 -2
- OpenApiLibCore/annotations.py +8 -1
- OpenApiLibCore/data_generation/__init__.py +0 -2
- OpenApiLibCore/data_generation/body_data_generation.py +52 -71
- OpenApiLibCore/data_generation/data_generation_core.py +82 -62
- OpenApiLibCore/data_invalidation.py +37 -20
- OpenApiLibCore/dto_base.py +20 -86
- OpenApiLibCore/localized_faker.py +88 -0
- OpenApiLibCore/models.py +715 -0
- OpenApiLibCore/openapi_libcore.libspec +47 -283
- OpenApiLibCore/openapi_libcore.py +20 -46
- OpenApiLibCore/parameter_utils.py +23 -17
- OpenApiLibCore/path_functions.py +5 -4
- OpenApiLibCore/protocols.py +7 -5
- OpenApiLibCore/request_data.py +67 -102
- OpenApiLibCore/resource_relations.py +2 -3
- OpenApiLibCore/validation.py +49 -161
- OpenApiLibCore/value_utils.py +46 -358
- openapi_libgen/__init__.py +0 -46
- openapi_libgen/command_line.py +7 -19
- openapi_libgen/generator.py +84 -0
- openapi_libgen/spec_parser.py +40 -113
- {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b4.dist-info}/METADATA +2 -1
- robotframework_openapitools-1.0.0b4.dist-info/RECORD +40 -0
- robotframework_openapitools-1.0.0b3.dist-info/RECORD +0 -37
- {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b4.dist-info}/LICENSE +0 -0
- {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b4.dist-info}/WHEEL +0 -0
- {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b4.dist-info}/entry_points.txt +0 -0
@@ -152,6 +152,11 @@ from OpenApiLibCore.dto_utils import (
|
|
152
152
|
get_dto_class,
|
153
153
|
get_id_property_name,
|
154
154
|
)
|
155
|
+
from OpenApiLibCore.localized_faker import FAKE
|
156
|
+
from OpenApiLibCore.models import (
|
157
|
+
OpenApiObject,
|
158
|
+
PathItemObject,
|
159
|
+
)
|
155
160
|
from OpenApiLibCore.oas_cache import PARSER_CACHE, CachedParser
|
156
161
|
from OpenApiLibCore.parameter_utils import (
|
157
162
|
get_oas_name_from_safe_name,
|
@@ -159,11 +164,10 @@ from OpenApiLibCore.parameter_utils import (
|
|
159
164
|
)
|
160
165
|
from OpenApiLibCore.protocols import ResponseValidatorType
|
161
166
|
from OpenApiLibCore.request_data import RequestData, RequestValues
|
162
|
-
from OpenApiLibCore.value_utils import FAKE
|
163
167
|
|
164
168
|
run_keyword = BuiltIn().run_keyword
|
165
169
|
default_str_mapping: Mapping[str, str] = MappingProxyType({})
|
166
|
-
|
170
|
+
default_json_mapping: Mapping[str, JSON] = MappingProxyType({})
|
167
171
|
|
168
172
|
|
169
173
|
@library(scope="SUITE", doc_format="ROBOT")
|
@@ -434,7 +438,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
434
438
|
self,
|
435
439
|
path: str,
|
436
440
|
method: str,
|
437
|
-
overrides: Mapping[str,
|
441
|
+
overrides: Mapping[str, JSON] = default_json_mapping,
|
438
442
|
) -> RequestValues:
|
439
443
|
"""Return an object with all (valid) request values needed to make a request."""
|
440
444
|
json_data: dict[str, JSON] = {}
|
@@ -482,24 +486,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
482
486
|
)
|
483
487
|
|
484
488
|
@keyword
|
485
|
-
def
|
486
|
-
self,
|
487
|
-
schema: dict[str, JSON],
|
488
|
-
dto_class: type[Dto],
|
489
|
-
operation_id: str = "",
|
490
|
-
) -> JSON:
|
491
|
-
"""
|
492
|
-
Generate valid (json-compatible) data for the `dto_class`.
|
493
|
-
"""
|
494
|
-
return _data_generation.get_json_data_for_dto_class(
|
495
|
-
schema=schema,
|
496
|
-
dto_class=dto_class,
|
497
|
-
get_id_property_name=self.get_id_property_name,
|
498
|
-
operation_id=operation_id,
|
499
|
-
)
|
500
|
-
|
501
|
-
@keyword
|
502
|
-
def get_invalid_json_data(
|
489
|
+
def get_invalid_body_data(
|
503
490
|
self,
|
504
491
|
url: str,
|
505
492
|
method: str,
|
@@ -513,7 +500,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
513
500
|
> Note: applicable UniquePropertyValueConstraint and IdReference Relations are
|
514
501
|
considered before changes to `json_data` are made.
|
515
502
|
"""
|
516
|
-
return di.
|
503
|
+
return di.get_invalid_body_data(
|
517
504
|
url=url,
|
518
505
|
method=method,
|
519
506
|
status_code=status_code,
|
@@ -526,7 +513,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
526
513
|
self,
|
527
514
|
status_code: int,
|
528
515
|
request_data: RequestData,
|
529
|
-
) -> tuple[dict[str, JSON], dict[str,
|
516
|
+
) -> tuple[dict[str, JSON], dict[str, JSON]]:
|
530
517
|
"""
|
531
518
|
Returns a version of `params, headers` as present on `request_data` that has
|
532
519
|
been modified to cause the provided `status_code`.
|
@@ -706,7 +693,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
706
693
|
path: str,
|
707
694
|
status_code: int,
|
708
695
|
request_values: RequestValues,
|
709
|
-
original_data: Mapping[str,
|
696
|
+
original_data: Mapping[str, JSON] = default_json_mapping,
|
710
697
|
) -> None:
|
711
698
|
"""
|
712
699
|
This keyword first calls the Authorized Request keyword, then the Validate
|
@@ -751,7 +738,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
751
738
|
self,
|
752
739
|
path: str,
|
753
740
|
response: Response,
|
754
|
-
original_data: Mapping[str,
|
741
|
+
original_data: Mapping[str, JSON] = default_json_mapping,
|
755
742
|
) -> None:
|
756
743
|
"""
|
757
744
|
Validate the `response` by performing the following validations:
|
@@ -775,24 +762,11 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
775
762
|
original_data=original_data,
|
776
763
|
)
|
777
764
|
|
778
|
-
@keyword
|
779
|
-
def validate_resource_properties(
|
780
|
-
self, resource: dict[str, JSON], schema: dict[str, JSON]
|
781
|
-
) -> None:
|
782
|
-
"""
|
783
|
-
Validate that the `resource` does not contain any properties that are not
|
784
|
-
defined in the `schema_properties`.
|
785
|
-
"""
|
786
|
-
val.validate_resource_properties(
|
787
|
-
resource=resource,
|
788
|
-
schema=schema,
|
789
|
-
)
|
790
|
-
|
791
765
|
@staticmethod
|
792
766
|
@keyword
|
793
767
|
def validate_send_response(
|
794
768
|
response: Response,
|
795
|
-
original_data: Mapping[str,
|
769
|
+
original_data: Mapping[str, JSON] = default_json_mapping,
|
796
770
|
) -> None:
|
797
771
|
"""
|
798
772
|
Validate that each property that was send that is in the response has the value
|
@@ -818,17 +792,17 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
818
792
|
return validation_spec
|
819
793
|
|
820
794
|
@property
|
821
|
-
def openapi_spec(self) ->
|
795
|
+
def openapi_spec(self) -> OpenApiObject:
|
822
796
|
"""Return a deepcopy of the parsed openapi document."""
|
823
797
|
# protect the parsed openapi spec from being mutated by reference
|
824
798
|
return deepcopy(self._openapi_spec)
|
825
799
|
|
826
800
|
@cached_property
|
827
|
-
def _openapi_spec(self) ->
|
801
|
+
def _openapi_spec(self) -> OpenApiObject:
|
828
802
|
parser, _, _ = self._load_specs_and_validator()
|
829
|
-
|
830
|
-
register_path_parameters(
|
831
|
-
return
|
803
|
+
spec_model = OpenApiObject.model_validate(parser.specification)
|
804
|
+
register_path_parameters(spec_model.paths)
|
805
|
+
return spec_model
|
832
806
|
|
833
807
|
@cached_property
|
834
808
|
def response_validator(
|
@@ -927,5 +901,5 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
927
901
|
f"ValidationError while trying to load openapi spec: {exception}"
|
928
902
|
) from exception
|
929
903
|
|
930
|
-
def read_paths(self) -> dict[str,
|
931
|
-
return self.openapi_spec
|
904
|
+
def read_paths(self) -> dict[str, PathItemObject]:
|
905
|
+
return self.openapi_spec.paths
|
@@ -5,7 +5,7 @@ names and the original names in the parsed OpenApi Specification document.
|
|
5
5
|
|
6
6
|
from typing import Generator
|
7
7
|
|
8
|
-
from OpenApiLibCore.
|
8
|
+
from OpenApiLibCore.models import ParameterObject, PathItemObject
|
9
9
|
|
10
10
|
PARAMETER_REGISTRY: dict[str, str] = {
|
11
11
|
"body": "body",
|
@@ -20,11 +20,11 @@ def get_oas_name_from_safe_name(safe_name: str) -> str:
|
|
20
20
|
|
21
21
|
|
22
22
|
def get_safe_name_for_oas_name(oas_name: str) -> str:
|
23
|
-
if
|
23
|
+
if _is_python_safe(oas_name):
|
24
24
|
PARAMETER_REGISTRY[oas_name] = oas_name
|
25
25
|
return oas_name
|
26
26
|
|
27
|
-
safe_name =
|
27
|
+
safe_name = _convert_string_to_python_identifier(oas_name)
|
28
28
|
|
29
29
|
if safe_name not in PARAMETER_REGISTRY:
|
30
30
|
PARAMETER_REGISTRY[safe_name] = oas_name
|
@@ -36,17 +36,17 @@ def get_safe_name_for_oas_name(oas_name: str) -> str:
|
|
36
36
|
# We're dealing with multiple oas_names that convert to the same safe_name.
|
37
37
|
# To resolve this, a more verbose safe_name is generated. This is less user-friendly
|
38
38
|
# but necessary to ensure an one-to-one mapping.
|
39
|
-
verbose_safe_name =
|
39
|
+
verbose_safe_name = _convert_string_to_python_identifier(oas_name, verbose=True)
|
40
40
|
if verbose_safe_name not in PARAMETER_REGISTRY:
|
41
41
|
PARAMETER_REGISTRY[verbose_safe_name] = oas_name
|
42
42
|
return verbose_safe_name
|
43
43
|
|
44
44
|
|
45
|
-
def
|
45
|
+
def _is_python_safe(name: str) -> bool:
|
46
46
|
return name.isidentifier()
|
47
47
|
|
48
48
|
|
49
|
-
def
|
49
|
+
def _convert_string_to_python_identifier(string: str, verbose: bool = False) -> str:
|
50
50
|
def _convert_string_to_python_identifier() -> Generator[str, None, None]:
|
51
51
|
string_iterator = iter(string)
|
52
52
|
|
@@ -71,21 +71,27 @@ def convert_string_to_python_identifier(string: str, verbose: bool = False) -> s
|
|
71
71
|
ascii_code = ord(character)
|
72
72
|
yield f"_{ascii_code}_"
|
73
73
|
|
74
|
-
if
|
74
|
+
if _is_python_safe(string):
|
75
75
|
return string
|
76
76
|
|
77
77
|
converted_string = "".join(_convert_string_to_python_identifier())
|
78
|
-
if not
|
78
|
+
if not _is_python_safe(converted_string):
|
79
79
|
raise ValueError(f"Failed to convert '{string}' to Python identifier.")
|
80
80
|
return converted_string
|
81
81
|
|
82
82
|
|
83
|
-
def register_path_parameters(paths_data: dict[str,
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
for
|
91
|
-
|
83
|
+
def register_path_parameters(paths_data: dict[str, PathItemObject]) -> None:
|
84
|
+
def _register_path_parameter(parameter_object: ParameterObject) -> None:
|
85
|
+
if parameter_object.in_ == "path":
|
86
|
+
_ = get_safe_name_for_oas_name(parameter_object.name)
|
87
|
+
|
88
|
+
for path_item in paths_data.values():
|
89
|
+
if parameters := path_item.parameters:
|
90
|
+
for parameter in path_item.parameters:
|
91
|
+
_register_path_parameter(parameter_object=parameter)
|
92
|
+
|
93
|
+
operations = path_item.get_operations()
|
94
|
+
for operation in operations.values():
|
95
|
+
if parameters := operation.parameters:
|
96
|
+
for parameter in parameters:
|
97
|
+
_register_path_parameter(parameter_object=parameter)
|
OpenApiLibCore/path_functions.py
CHANGED
@@ -9,6 +9,7 @@ from requests import Response
|
|
9
9
|
from robot.libraries.BuiltIn import BuiltIn
|
10
10
|
|
11
11
|
from OpenApiLibCore.dto_base import PathPropertiesConstraint
|
12
|
+
from OpenApiLibCore.models import OpenApiObject
|
12
13
|
from OpenApiLibCore.protocols import GetDtoClassType, GetIdPropertyNameType
|
13
14
|
from OpenApiLibCore.request_data import RequestData
|
14
15
|
|
@@ -24,14 +25,14 @@ def match_parts(parts: list[str], spec_parts: list[str]) -> bool:
|
|
24
25
|
return True
|
25
26
|
|
26
27
|
|
27
|
-
def get_parametrized_path(path: str, openapi_spec:
|
28
|
+
def get_parametrized_path(path: str, openapi_spec: OpenApiObject) -> str:
|
28
29
|
path_parts = path.split("/")
|
29
30
|
# if the last part is empty, the path has a trailing `/` that
|
30
31
|
# should be ignored during matching
|
31
32
|
if path_parts[-1] == "":
|
32
33
|
_ = path_parts.pop(-1)
|
33
34
|
|
34
|
-
spec_paths: list[str] =
|
35
|
+
spec_paths: list[str] = list(openapi_spec.paths.keys())
|
35
36
|
|
36
37
|
candidates: list[str] = []
|
37
38
|
|
@@ -64,12 +65,12 @@ def get_valid_url(
|
|
64
65
|
path: str,
|
65
66
|
base_url: str,
|
66
67
|
get_dto_class: GetDtoClassType,
|
67
|
-
openapi_spec:
|
68
|
+
openapi_spec: OpenApiObject,
|
68
69
|
) -> str:
|
69
70
|
try:
|
70
71
|
# path can be partially resolved or provided by a PathPropertiesConstraint
|
71
72
|
parametrized_path = get_parametrized_path(path=path, openapi_spec=openapi_spec)
|
72
|
-
_ = openapi_spec
|
73
|
+
_ = openapi_spec.paths[parametrized_path]
|
73
74
|
except KeyError:
|
74
75
|
raise ValueError(
|
75
76
|
f"{path} not found in paths section of the OpenAPI document."
|
OpenApiLibCore/protocols.py
CHANGED
@@ -13,18 +13,20 @@ from OpenApiLibCore.dto_base import Dto
|
|
13
13
|
class ResponseValidatorType(Protocol):
|
14
14
|
def __call__(
|
15
15
|
self, request: RequestsOpenAPIRequest, response: RequestsOpenAPIResponse
|
16
|
-
) -> None: ...
|
16
|
+
) -> None: ... # pragma: no cover
|
17
17
|
|
18
18
|
|
19
19
|
class GetDtoClassType(Protocol):
|
20
|
-
def __init__(self, mappings_module_name: str) -> None: ...
|
20
|
+
def __init__(self, mappings_module_name: str) -> None: ... # pragma: no cover
|
21
21
|
|
22
|
-
def __call__(self, path: str, method: str) -> Type[Dto]: ...
|
22
|
+
def __call__(self, path: str, method: str) -> Type[Dto]: ... # pragma: no cover
|
23
23
|
|
24
24
|
|
25
25
|
class GetIdPropertyNameType(Protocol):
|
26
|
-
def __init__(self, mappings_module_name: str) -> None: ...
|
26
|
+
def __init__(self, mappings_module_name: str) -> None: ... # pragma: no cover
|
27
27
|
|
28
28
|
def __call__(
|
29
29
|
self, path: str
|
30
|
-
) -> tuple[
|
30
|
+
) -> tuple[
|
31
|
+
str, Callable[[str], str] | Callable[[int], int]
|
32
|
+
]: ... # pragma: no cover
|
OpenApiLibCore/request_data.py
CHANGED
@@ -6,11 +6,15 @@ from functools import cached_property
|
|
6
6
|
from random import sample
|
7
7
|
from typing import Any
|
8
8
|
|
9
|
-
from OpenApiLibCore.
|
10
|
-
|
11
|
-
resolve_schema,
|
12
|
-
)
|
9
|
+
from OpenApiLibCore.annotations import JSON
|
10
|
+
from OpenApiLibCore.dto_base import Dto
|
13
11
|
from OpenApiLibCore.dto_utils import DefaultDto
|
12
|
+
from OpenApiLibCore.models import (
|
13
|
+
ObjectSchema,
|
14
|
+
ParameterObject,
|
15
|
+
ResolvedSchemaObjectTypes,
|
16
|
+
UnionTypeSchema,
|
17
|
+
)
|
14
18
|
|
15
19
|
|
16
20
|
@dataclass
|
@@ -19,23 +23,23 @@ class RequestValues:
|
|
19
23
|
|
20
24
|
url: str
|
21
25
|
method: str
|
22
|
-
params: dict[str,
|
23
|
-
headers: dict[str,
|
24
|
-
json_data: dict[str,
|
26
|
+
params: dict[str, JSON] = field(default_factory=dict)
|
27
|
+
headers: dict[str, JSON] = field(default_factory=dict)
|
28
|
+
json_data: dict[str, JSON] = field(default_factory=dict)
|
25
29
|
|
26
|
-
def override_body_value(self, name: str, value:
|
30
|
+
def override_body_value(self, name: str, value: JSON) -> None:
|
27
31
|
if name in self.json_data:
|
28
32
|
self.json_data[name] = value
|
29
33
|
|
30
|
-
def override_header_value(self, name: str, value:
|
34
|
+
def override_header_value(self, name: str, value: JSON) -> None:
|
31
35
|
if name in self.headers:
|
32
36
|
self.headers[name] = value
|
33
37
|
|
34
|
-
def override_param_value(self, name: str, value:
|
38
|
+
def override_param_value(self, name: str, value: JSON) -> None:
|
35
39
|
if name in self.params:
|
36
40
|
self.params[name] = str(value)
|
37
41
|
|
38
|
-
def override_request_value(self, name: str, value:
|
42
|
+
def override_request_value(self, name: str, value: JSON) -> None:
|
39
43
|
self.override_body_value(name=name, value=value)
|
40
44
|
self.override_header_value(name=name, value=value)
|
41
45
|
self.override_param_value(name=name, value=value)
|
@@ -52,16 +56,14 @@ class RequestData:
|
|
52
56
|
"""Helper class to manage parameters used when making requests."""
|
53
57
|
|
54
58
|
dto: Dto | DefaultDto = field(default_factory=DefaultDto)
|
55
|
-
|
56
|
-
parameters: list[
|
57
|
-
params: dict[str,
|
58
|
-
headers: dict[str,
|
59
|
+
body_schema: ObjectSchema | None = None
|
60
|
+
parameters: list[ParameterObject] = field(default_factory=list)
|
61
|
+
params: dict[str, JSON] = field(default_factory=dict)
|
62
|
+
headers: dict[str, JSON] = field(default_factory=dict)
|
59
63
|
has_body: bool = True
|
60
64
|
|
61
65
|
def __post_init__(self) -> None:
|
62
66
|
# prevent modification by reference
|
63
|
-
self.dto_schema = deepcopy(self.dto_schema)
|
64
|
-
self.parameters = deepcopy(self.parameters)
|
65
67
|
self.params = deepcopy(self.params)
|
66
68
|
self.headers = deepcopy(self.headers)
|
67
69
|
|
@@ -70,20 +72,24 @@ class RequestData:
|
|
70
72
|
"""Whether or not the dto data (json data) contains optional properties."""
|
71
73
|
|
72
74
|
def is_required_property(property_name: str) -> bool:
|
73
|
-
return property_name in self.
|
75
|
+
return property_name in self.required_property_names
|
74
76
|
|
75
77
|
properties = (self.dto.as_dict()).keys()
|
76
78
|
return not all(map(is_required_property, properties))
|
77
79
|
|
80
|
+
@property
|
81
|
+
def required_property_names(self) -> list[str]:
|
82
|
+
if self.body_schema:
|
83
|
+
return self.body_schema.required
|
84
|
+
return []
|
85
|
+
|
78
86
|
@property
|
79
87
|
def has_optional_params(self) -> bool:
|
80
88
|
"""Whether or not any of the query parameters are optional."""
|
81
89
|
|
82
90
|
def is_optional_param(query_param: str) -> bool:
|
83
91
|
optional_params = [
|
84
|
-
p.
|
85
|
-
for p in self.parameters
|
86
|
-
if p.get("in") == "query" and not p.get("required")
|
92
|
+
p.name for p in self.parameters if p.in_ == "query" and not p.required
|
87
93
|
]
|
88
94
|
return query_param in optional_params
|
89
95
|
|
@@ -96,47 +102,26 @@ class RequestData:
|
|
96
102
|
restrictions, data type or by not providing them in a request.
|
97
103
|
"""
|
98
104
|
result = set()
|
99
|
-
params = [h for h in self.parameters if h.
|
105
|
+
params = [h for h in self.parameters if h.in_ == "query"]
|
100
106
|
for param in params:
|
101
107
|
# required params can be omitted to invalidate a request
|
102
|
-
if param
|
103
|
-
result.add(param
|
108
|
+
if param.required:
|
109
|
+
result.add(param.name)
|
104
110
|
continue
|
105
111
|
|
106
|
-
|
107
|
-
|
108
|
-
|
112
|
+
if param.schema_ is None:
|
113
|
+
continue
|
114
|
+
|
115
|
+
possible_schemas: list[ResolvedSchemaObjectTypes] = []
|
116
|
+
if isinstance(param.schema_, UnionTypeSchema):
|
117
|
+
possible_schemas = param.schema_.resolved_schemas
|
109
118
|
else:
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
continue
|
117
|
-
# enums, strings and arrays with boundaries can be invalidated
|
118
|
-
if set(param_type.keys()).intersection(
|
119
|
-
{
|
120
|
-
"enum",
|
121
|
-
"minLength",
|
122
|
-
"maxLength",
|
123
|
-
"minItems",
|
124
|
-
"maxItems",
|
125
|
-
}
|
126
|
-
):
|
127
|
-
result.add(param["name"])
|
128
|
-
continue
|
129
|
-
# an array of basic non-string type can be invalidated by replacing the
|
130
|
-
# items in the array with strings
|
131
|
-
if param_type["type"] == "array" and param_type["items"][
|
132
|
-
"type"
|
133
|
-
] not in [
|
134
|
-
"string",
|
135
|
-
"array",
|
136
|
-
"object",
|
137
|
-
"null",
|
138
|
-
]:
|
139
|
-
result.add(param["name"])
|
119
|
+
possible_schemas = [param.schema_]
|
120
|
+
|
121
|
+
for param_schema in possible_schemas:
|
122
|
+
if param_schema.can_be_invalidated:
|
123
|
+
result.add(param.name)
|
124
|
+
|
140
125
|
return result
|
141
126
|
|
142
127
|
@property
|
@@ -145,9 +130,7 @@ class RequestData:
|
|
145
130
|
|
146
131
|
def is_optional_header(header: str) -> bool:
|
147
132
|
optional_headers = [
|
148
|
-
p.
|
149
|
-
for p in self.parameters
|
150
|
-
if p.get("in") == "header" and not p.get("required")
|
133
|
+
p.name for p in self.parameters if p.in_ == "header" and not p.required
|
151
134
|
]
|
152
135
|
return header in optional_headers
|
153
136
|
|
@@ -160,47 +143,26 @@ class RequestData:
|
|
160
143
|
restrictions or by not providing them in a request.
|
161
144
|
"""
|
162
145
|
result = set()
|
163
|
-
headers = [h for h in self.parameters if h.
|
146
|
+
headers = [h for h in self.parameters if h.in_ == "header"]
|
164
147
|
for header in headers:
|
165
148
|
# required headers can be omitted to invalidate a request
|
166
|
-
if header
|
167
|
-
result.add(header
|
149
|
+
if header.required:
|
150
|
+
result.add(header.name)
|
151
|
+
continue
|
152
|
+
|
153
|
+
if header.schema_ is None:
|
168
154
|
continue
|
169
155
|
|
170
|
-
|
171
|
-
if
|
172
|
-
|
156
|
+
possible_schemas: list[ResolvedSchemaObjectTypes] = []
|
157
|
+
if isinstance(header.schema_, UnionTypeSchema):
|
158
|
+
possible_schemas = header.schema_.resolved_schemas
|
173
159
|
else:
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
continue
|
181
|
-
# enums, strings and arrays with boundaries can be invalidated
|
182
|
-
if set(header_type.keys()).intersection(
|
183
|
-
{
|
184
|
-
"enum",
|
185
|
-
"minLength",
|
186
|
-
"maxLength",
|
187
|
-
"minItems",
|
188
|
-
"maxItems",
|
189
|
-
}
|
190
|
-
):
|
191
|
-
result.add(header["name"])
|
192
|
-
continue
|
193
|
-
# an array of basic non-string type can be invalidated by replacing the
|
194
|
-
# items in the array with strings
|
195
|
-
if header_type["type"] == "array" and header_type["items"][
|
196
|
-
"type"
|
197
|
-
] not in [
|
198
|
-
"string",
|
199
|
-
"array",
|
200
|
-
"object",
|
201
|
-
"null",
|
202
|
-
]:
|
203
|
-
result.add(header["name"])
|
160
|
+
possible_schemas = [header.schema_]
|
161
|
+
|
162
|
+
for param_schema in possible_schemas:
|
163
|
+
if param_schema.can_be_invalidated:
|
164
|
+
result.add(header.name)
|
165
|
+
|
204
166
|
return result
|
205
167
|
|
206
168
|
def get_required_properties_dict(self) -> dict[str, Any]:
|
@@ -211,7 +173,7 @@ class RequestData:
|
|
211
173
|
for relation in relations
|
212
174
|
if getattr(relation, "treat_as_mandatory", False)
|
213
175
|
]
|
214
|
-
required_properties
|
176
|
+
required_properties = self.body_schema.required if self.body_schema else []
|
215
177
|
required_properties.extend(mandatory_properties)
|
216
178
|
|
217
179
|
required_properties_dict: dict[str, Any] = {}
|
@@ -223,7 +185,10 @@ class RequestData:
|
|
223
185
|
def get_minimal_body_dict(self) -> dict[str, Any]:
|
224
186
|
required_properties_dict = self.get_required_properties_dict()
|
225
187
|
|
226
|
-
min_properties =
|
188
|
+
min_properties = 0
|
189
|
+
if self.body_schema and self.body_schema.minProperties is not None:
|
190
|
+
min_properties = self.body_schema.minProperties
|
191
|
+
|
227
192
|
number_of_optional_properties_to_add = min_properties - len(
|
228
193
|
required_properties_dict
|
229
194
|
)
|
@@ -247,13 +212,13 @@ class RequestData:
|
|
247
212
|
|
248
213
|
return {**required_properties_dict, **optional_properties_dict}
|
249
214
|
|
250
|
-
def get_required_params(self) -> dict[str,
|
215
|
+
def get_required_params(self) -> dict[str, JSON]:
|
251
216
|
"""Get the params dict containing only the required query parameters."""
|
252
217
|
return {
|
253
218
|
k: v for k, v in self.params.items() if k in self.required_parameter_names
|
254
219
|
}
|
255
220
|
|
256
|
-
def get_required_headers(self) -> dict[str,
|
221
|
+
def get_required_headers(self) -> dict[str, JSON]:
|
257
222
|
"""Get the headers dict containing only the required headers."""
|
258
223
|
return {
|
259
224
|
k: v for k, v in self.headers.items() if k in self.required_parameter_names
|
@@ -271,11 +236,11 @@ class RequestData:
|
|
271
236
|
for relation in relations
|
272
237
|
if getattr(relation, "treat_as_mandatory", False)
|
273
238
|
]
|
274
|
-
parameter_names = [p
|
239
|
+
parameter_names = [p.name for p in self.parameters]
|
275
240
|
mandatory_parameters = [
|
276
241
|
p for p in mandatory_property_names if p in parameter_names
|
277
242
|
]
|
278
243
|
|
279
|
-
required_parameters = [p
|
244
|
+
required_parameters = [p.name for p in self.parameters if p.required]
|
280
245
|
required_parameters.extend(mandatory_parameters)
|
281
246
|
return required_parameters
|
@@ -1,13 +1,12 @@
|
|
1
1
|
"""Module holding the functions related to relations between resources."""
|
2
2
|
|
3
|
-
from typing import Any
|
4
|
-
|
5
3
|
from requests import Response
|
6
4
|
from robot.api import logger
|
7
5
|
from robot.libraries.BuiltIn import BuiltIn
|
8
6
|
|
9
7
|
import OpenApiLibCore.path_functions as pf
|
10
8
|
from OpenApiLibCore.dto_base import IdReference
|
9
|
+
from OpenApiLibCore.models import OpenApiObject
|
11
10
|
from OpenApiLibCore.request_data import RequestData
|
12
11
|
|
13
12
|
run_keyword = BuiltIn().run_keyword
|
@@ -16,7 +15,7 @@ run_keyword = BuiltIn().run_keyword
|
|
16
15
|
def ensure_in_use(
|
17
16
|
url: str,
|
18
17
|
base_url: str,
|
19
|
-
openapi_spec:
|
18
|
+
openapi_spec: OpenApiObject,
|
20
19
|
resource_relation: IdReference,
|
21
20
|
) -> None:
|
22
21
|
resource_id = ""
|