robotframework-openapitools 1.0.0b4__tar.gz → 1.0.0b5__tar.gz
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.
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/PKG-INFO +1 -1
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/pyproject.toml +1 -1
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/openapi_executors.py +7 -3
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/openapidriver.libspec +3 -3
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/__init__.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/data_generation/__init__.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/data_generation/body_data_generation.py +4 -4
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/data_generation/data_generation_core.py +18 -45
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/data_invalidation.py +1 -5
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/dto_base.py +31 -22
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/dto_utils.py +31 -3
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/localized_faker.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/models.py +26 -18
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/openapi_libcore.libspec +26 -26
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/openapi_libcore.py +35 -26
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/parameter_utils.py +3 -3
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/path_functions.py +5 -6
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/path_invalidation.py +5 -7
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/protocols.py +6 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/request_data.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/resource_relations.py +4 -2
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/validation.py +4 -9
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/value_utils.py +1 -1
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/generator.py +2 -2
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/parsing_utils.py +9 -5
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/spec_parser.py +4 -4
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/LICENSE +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/docs/README.md +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/__init__.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/openapi_reader.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/openapidriver.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/py.typed +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/annotations.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/oas_cache.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/py.typed +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/__init__.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/command_line.py +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/templates/__init__.jinja +0 -0
- {robotframework_openapitools-1.0.0b4 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/templates/library.jinja +0 -0
@@ -133,11 +133,15 @@ class OpenApiExecutors(OpenApiLibCore):
|
|
133
133
|
"""
|
134
134
|
valid_url: str = run_keyword("get_valid_url", path)
|
135
135
|
|
136
|
-
|
137
|
-
url
|
136
|
+
try:
|
137
|
+
url = run_keyword(
|
138
138
|
"get_invalidated_url", valid_url, path, expected_status_code
|
139
139
|
)
|
140
|
-
|
140
|
+
except Exception as exception:
|
141
|
+
message = getattr(exception, "message", "")
|
142
|
+
if not message.startswith("ValueError"):
|
143
|
+
raise exception # pragma: no cover
|
144
|
+
|
141
145
|
raise SkipExecution(
|
142
146
|
f"Path {path} does not contain resource references that "
|
143
147
|
f"can be invalidated."
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<keywordspec name="OpenApiDriver" type="LIBRARY" format="HTML" scope="SUITE" generated="2025-
|
3
|
-
<version>1.0.
|
2
|
+
<keywordspec name="OpenApiDriver" type="LIBRARY" format="HTML" scope="SUITE" generated="2025-06-09T18:38:35+00:00" specversion="6" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapidriver.py" lineno="358">
|
3
|
+
<version>1.0.0b5</version>
|
4
4
|
<doc><p>Visit the <a href="https://github.com/MarketSquare/robotframework-openapidriver">library page</a> for an introduction and examples.</p></doc>
|
5
5
|
<tags>
|
6
6
|
</tags>
|
@@ -247,7 +247,7 @@
|
|
247
247
|
</init>
|
248
248
|
</inits>
|
249
249
|
<keywords>
|
250
|
-
<kw name="Test Endpoint" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapi_executors.py" lineno="
|
250
|
+
<kw name="Test Endpoint" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapi_executors.py" lineno="166">
|
251
251
|
<arguments repr="path: str, method: str, status_code: int">
|
252
252
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
|
253
253
|
<name>path</name>
|
File without changes
|
File without changes
|
@@ -8,7 +8,7 @@ from typing import Any
|
|
8
8
|
|
9
9
|
from robot.api import logger
|
10
10
|
|
11
|
-
import OpenApiLibCore.path_functions as
|
11
|
+
import OpenApiLibCore.path_functions as _path_functions
|
12
12
|
from OpenApiLibCore.annotations import JSON
|
13
13
|
from OpenApiLibCore.dto_base import (
|
14
14
|
Dto,
|
@@ -72,7 +72,7 @@ def get_dict_data_for_dto_class(
|
|
72
72
|
property_names = get_property_names_to_process(schema=schema, dto_class=dto_class)
|
73
73
|
|
74
74
|
for property_name in property_names:
|
75
|
-
property_schema = schema.properties.root[property_name]
|
75
|
+
property_schema = schema.properties.root[property_name] # type: ignore[union-attr]
|
76
76
|
if property_schema.readOnly:
|
77
77
|
continue
|
78
78
|
|
@@ -178,7 +178,7 @@ def get_property_names_to_process(
|
|
178
178
|
) -> list[str]:
|
179
179
|
property_names = []
|
180
180
|
|
181
|
-
for property_name in schema.properties.root:
|
181
|
+
for property_name in schema.properties.root: # type: ignore[union-attr]
|
182
182
|
# register the oas_name
|
183
183
|
_ = get_safe_name_for_oas_name(property_name)
|
184
184
|
if constrained_values := get_constrained_values(
|
@@ -243,7 +243,7 @@ def get_dependent_id(
|
|
243
243
|
except ValueError:
|
244
244
|
return None
|
245
245
|
|
246
|
-
valid_id =
|
246
|
+
valid_id = _path_functions.get_valid_id_for_path(
|
247
247
|
path=id_get_path, get_id_property_name=get_id_property_name
|
248
248
|
)
|
249
249
|
logger.debug(f"get_dependent_id for {id_get_path} returned {valid_id}")
|
@@ -3,14 +3,13 @@ Module holding the main functions related to data generation
|
|
3
3
|
for the requests made as part of keyword exection.
|
4
4
|
"""
|
5
5
|
|
6
|
-
import re
|
7
6
|
from dataclasses import Field, field, make_dataclass
|
8
7
|
from random import choice
|
9
8
|
from typing import Any, cast
|
10
9
|
|
11
10
|
from robot.api import logger
|
12
11
|
|
13
|
-
import OpenApiLibCore.path_functions as
|
12
|
+
import OpenApiLibCore.path_functions as _path_functions
|
14
13
|
from OpenApiLibCore.annotations import JSON
|
15
14
|
from OpenApiLibCore.dto_base import (
|
16
15
|
Dto,
|
@@ -23,7 +22,6 @@ from OpenApiLibCore.models import (
|
|
23
22
|
OpenApiObject,
|
24
23
|
OperationObject,
|
25
24
|
ParameterObject,
|
26
|
-
RequestBodyObject,
|
27
25
|
UnionTypeSchema,
|
28
26
|
)
|
29
27
|
from OpenApiLibCore.parameter_utils import get_safe_name_for_oas_name
|
@@ -47,11 +45,13 @@ def get_request_data(
|
|
47
45
|
dto_cls_name = get_dto_cls_name(path=path, method=method)
|
48
46
|
# The path can contain already resolved Ids that have to be matched
|
49
47
|
# against the parametrized paths in the paths section.
|
50
|
-
spec_path =
|
48
|
+
spec_path = _path_functions.get_parametrized_path(
|
49
|
+
path=path, openapi_spec=openapi_spec
|
50
|
+
)
|
51
51
|
dto_class = get_dto_class(path=spec_path, method=method)
|
52
52
|
try:
|
53
53
|
path_item = openapi_spec.paths[spec_path]
|
54
|
-
operation_spec = getattr(path_item, method)
|
54
|
+
operation_spec: OperationObject | None = getattr(path_item, method)
|
55
55
|
if operation_spec is None:
|
56
56
|
raise AttributeError
|
57
57
|
except AttributeError:
|
@@ -77,38 +77,30 @@ def get_request_data(
|
|
77
77
|
has_body=False,
|
78
78
|
)
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
body_schema = operation_spec.requestBody
|
83
|
-
media_type_dict = body_schema.content
|
84
|
-
supported_types = [v for k, v in media_type_dict.items() if "json" in k]
|
85
|
-
supported_schemas = [t.schema_ for t in supported_types if t.schema_ is not None]
|
80
|
+
body_schema = operation_spec.requestBody.schema_
|
86
81
|
|
87
|
-
if not
|
88
|
-
raise ValueError(
|
89
|
-
|
90
|
-
if len(supported_schemas) > 1:
|
91
|
-
logger.warn(
|
92
|
-
f"Multiple JSON media types defined for requestBody, using the first candidate {media_type_dict}"
|
82
|
+
if not body_schema:
|
83
|
+
raise ValueError(
|
84
|
+
f"No supported content schema found: {operation_spec.requestBody.content}"
|
93
85
|
)
|
94
86
|
|
95
|
-
|
87
|
+
headers.update({"content-type": operation_spec.requestBody.mime_type})
|
96
88
|
|
97
|
-
if isinstance(
|
98
|
-
resolved_schemas =
|
99
|
-
|
89
|
+
if isinstance(body_schema, UnionTypeSchema):
|
90
|
+
resolved_schemas = body_schema.resolved_schemas
|
91
|
+
body_schema = choice(resolved_schemas)
|
100
92
|
|
101
|
-
if not isinstance(
|
102
|
-
raise ValueError(f"Selected schema is not an object schema: {
|
93
|
+
if not isinstance(body_schema, ObjectSchema):
|
94
|
+
raise ValueError(f"Selected schema is not an object schema: {body_schema}")
|
103
95
|
|
104
96
|
dto_data = _get_json_data_for_dto_class(
|
105
|
-
schema=
|
97
|
+
schema=body_schema,
|
106
98
|
dto_class=dto_class,
|
107
99
|
get_id_property_name=get_id_property_name,
|
108
100
|
operation_id=operation_spec.operationId,
|
109
101
|
)
|
110
102
|
dto_instance = _get_dto_instance_from_dto_data(
|
111
|
-
object_schema=
|
103
|
+
object_schema=body_schema,
|
112
104
|
dto_class=dto_class,
|
113
105
|
dto_data=dto_data,
|
114
106
|
method_spec=operation_spec,
|
@@ -116,7 +108,7 @@ def get_request_data(
|
|
116
108
|
)
|
117
109
|
return RequestData(
|
118
110
|
dto=dto_instance,
|
119
|
-
body_schema=
|
111
|
+
body_schema=body_schema,
|
120
112
|
parameters=parameters,
|
121
113
|
params=params,
|
122
114
|
headers=headers,
|
@@ -198,25 +190,6 @@ def get_dto_cls_name(path: str, method: str) -> str:
|
|
198
190
|
return result
|
199
191
|
|
200
192
|
|
201
|
-
def get_content_type(body_spec: RequestBodyObject) -> str:
|
202
|
-
"""Get and validate the first supported content type from the requested body spec
|
203
|
-
|
204
|
-
Should be application/json like content type,
|
205
|
-
e.g "application/json;charset=utf-8" or "application/merge-patch+json"
|
206
|
-
"""
|
207
|
-
content_types: list[str] = list(body_spec.content.keys())
|
208
|
-
json_regex = r"application/([a-z\-]+\+)?json(;\s?charset=(.+))?"
|
209
|
-
for content_type in content_types:
|
210
|
-
if re.search(json_regex, content_type):
|
211
|
-
return content_type
|
212
|
-
|
213
|
-
# At present no supported for other types.
|
214
|
-
raise NotImplementedError(
|
215
|
-
f"Only content types like 'application/json' are supported. "
|
216
|
-
f"Content types definded in the spec are '{content_types}'."
|
217
|
-
)
|
218
|
-
|
219
|
-
|
220
193
|
def get_request_parameters(
|
221
194
|
dto_class: Dto | type[Dto], method_spec: OperationObject
|
222
195
|
) -> tuple[list[ParameterObject], dict[str, Any], dict[str, str]]:
|
@@ -16,7 +16,6 @@ from OpenApiLibCore.dto_base import (
|
|
16
16
|
NOT_SET,
|
17
17
|
Dto,
|
18
18
|
IdReference,
|
19
|
-
PathPropertiesConstraint,
|
20
19
|
PropertyValueConstraint,
|
21
20
|
UniquePropertyValueConstraint,
|
22
21
|
)
|
@@ -35,10 +34,7 @@ def get_invalid_body_data(
|
|
35
34
|
invalid_property_default_response: int,
|
36
35
|
) -> dict[str, Any]:
|
37
36
|
method = method.lower()
|
38
|
-
data_relations = request_data.dto.
|
39
|
-
data_relations = [
|
40
|
-
r for r in data_relations if not isinstance(r, PathPropertiesConstraint)
|
41
|
-
]
|
37
|
+
data_relations = request_data.dto.get_body_relations_for_error_code(status_code)
|
42
38
|
if not data_relations:
|
43
39
|
if request_data.body_schema is None:
|
44
40
|
raise ValueError(
|
@@ -83,17 +83,17 @@ class Dto(ABC):
|
|
83
83
|
"""Base class for the Dto class."""
|
84
84
|
|
85
85
|
@staticmethod
|
86
|
-
def
|
86
|
+
def get_path_relations() -> list[PathPropertiesConstraint]:
|
87
87
|
"""Return the list of Relations for the header and query parameters."""
|
88
88
|
return []
|
89
89
|
|
90
|
-
def
|
90
|
+
def get_path_relations_for_error_code(
|
91
91
|
self, error_code: int
|
92
|
-
) -> list[
|
92
|
+
) -> list[PathPropertiesConstraint]:
|
93
93
|
"""Return the list of Relations associated with the given error_code."""
|
94
|
-
relations: list[
|
94
|
+
relations: list[PathPropertiesConstraint] = [
|
95
95
|
r
|
96
|
-
for r in self.
|
96
|
+
for r in self.get_path_relations()
|
97
97
|
if r.error_code == error_code
|
98
98
|
or (
|
99
99
|
getattr(r, "invalid_value_error_code", None) == error_code
|
@@ -103,15 +103,17 @@ class Dto(ABC):
|
|
103
103
|
return relations
|
104
104
|
|
105
105
|
@staticmethod
|
106
|
-
def
|
107
|
-
"""Return the list of Relations for the
|
106
|
+
def get_parameter_relations() -> list[ResourceRelation]:
|
107
|
+
"""Return the list of Relations for the header and query parameters."""
|
108
108
|
return []
|
109
109
|
|
110
|
-
def
|
110
|
+
def get_parameter_relations_for_error_code(
|
111
|
+
self, error_code: int
|
112
|
+
) -> list[ResourceRelation]:
|
111
113
|
"""Return the list of Relations associated with the given error_code."""
|
112
114
|
relations: list[ResourceRelation] = [
|
113
115
|
r
|
114
|
-
for r in self.
|
116
|
+
for r in self.get_parameter_relations()
|
115
117
|
if r.error_code == error_code
|
116
118
|
or (
|
117
119
|
getattr(r, "invalid_value_error_code", None) == error_code
|
@@ -120,6 +122,11 @@ class Dto(ABC):
|
|
120
122
|
]
|
121
123
|
return relations
|
122
124
|
|
125
|
+
@staticmethod
|
126
|
+
def get_relations() -> list[ResourceRelation]:
|
127
|
+
"""Return the list of Relations for the (json) body."""
|
128
|
+
return []
|
129
|
+
|
123
130
|
def get_body_relations_for_error_code(
|
124
131
|
self, error_code: int
|
125
132
|
) -> list[ResourceRelation]:
|
@@ -127,8 +134,16 @@ class Dto(ABC):
|
|
127
134
|
Return the list of Relations associated with the given error_code that are
|
128
135
|
applicable to the body / payload of the request.
|
129
136
|
"""
|
130
|
-
|
131
|
-
|
137
|
+
relations: list[ResourceRelation] = [
|
138
|
+
r
|
139
|
+
for r in self.get_relations()
|
140
|
+
if r.error_code == error_code
|
141
|
+
or (
|
142
|
+
getattr(r, "invalid_value_error_code", None) == error_code
|
143
|
+
and getattr(r, "invalid_value", None) != NOT_SET
|
144
|
+
)
|
145
|
+
]
|
146
|
+
return relations
|
132
147
|
|
133
148
|
def get_invalidated_data(
|
134
149
|
self,
|
@@ -139,17 +154,11 @@ class Dto(ABC):
|
|
139
154
|
"""Return a data set with one of the properties set to an invalid value or type."""
|
140
155
|
properties: dict[str, Any] = self.as_dict()
|
141
156
|
|
142
|
-
|
143
|
-
|
144
|
-
relations = self.get_relations_for_error_code(error_code=status_code)
|
145
|
-
# filter PathProperyConstraints since in that case no data can be invalidated
|
146
|
-
relations = [
|
147
|
-
r for r in relations if not isinstance(r, PathPropertiesConstraint)
|
148
|
-
]
|
157
|
+
relations = self.get_body_relations_for_error_code(error_code=status_code)
|
149
158
|
property_names = [r.property_name for r in relations]
|
150
159
|
if status_code == invalid_property_default_code:
|
151
160
|
# add all properties defined in the schema, including optional properties
|
152
|
-
property_names.extend((schema.properties.root.keys()))
|
161
|
+
property_names.extend((schema.properties.root.keys())) # type: ignore[union-attr]
|
153
162
|
if not property_names:
|
154
163
|
raise ValueError(
|
155
164
|
f"No property can be invalidated to cause status_code {status_code}"
|
@@ -167,8 +176,8 @@ class Dto(ABC):
|
|
167
176
|
if id_dependencies:
|
168
177
|
invalid_id = uuid4().hex
|
169
178
|
logger.debug(
|
170
|
-
f"Breaking IdDependency for status_code {status_code}:
|
171
|
-
f"{
|
179
|
+
f"Breaking IdDependency for status_code {status_code}: setting "
|
180
|
+
f"{property_name} to {invalid_id}"
|
172
181
|
)
|
173
182
|
properties[property_name] = invalid_id
|
174
183
|
return properties
|
@@ -191,7 +200,7 @@ class Dto(ABC):
|
|
191
200
|
)
|
192
201
|
return properties
|
193
202
|
|
194
|
-
value_schema = schema.properties.root[property_name]
|
203
|
+
value_schema = schema.properties.root[property_name] # type: ignore[union-attr]
|
195
204
|
if isinstance(value_schema, UnionTypeSchema):
|
196
205
|
# Filter "type": "null" from the possible types since this indicates an
|
197
206
|
# optional / nullable property that can only be invalidated by sending
|
@@ -7,7 +7,11 @@ from typing import Any, Callable, Type, overload
|
|
7
7
|
from robot.api import logger
|
8
8
|
|
9
9
|
from OpenApiLibCore.dto_base import Dto
|
10
|
-
from OpenApiLibCore.protocols import
|
10
|
+
from OpenApiLibCore.protocols import (
|
11
|
+
GetDtoClassType,
|
12
|
+
GetIdPropertyNameType,
|
13
|
+
GetPathDtoClassType,
|
14
|
+
)
|
11
15
|
|
12
16
|
|
13
17
|
@dataclass
|
@@ -49,6 +53,30 @@ class GetDtoClass:
|
|
49
53
|
return DefaultDto
|
50
54
|
|
51
55
|
|
56
|
+
def get_path_dto_class(mappings_module_name: str) -> GetPathDtoClassType:
|
57
|
+
return GetPathDtoClass(mappings_module_name=mappings_module_name)
|
58
|
+
|
59
|
+
|
60
|
+
class GetPathDtoClass:
|
61
|
+
"""Callable class to return Dtos from user-implemented mappings file."""
|
62
|
+
|
63
|
+
def __init__(self, mappings_module_name: str) -> None:
|
64
|
+
try:
|
65
|
+
mappings_module = import_module(mappings_module_name)
|
66
|
+
self.dto_mapping: dict[str, Type[Dto]] = mappings_module.PATH_MAPPING
|
67
|
+
except (ImportError, AttributeError, ValueError) as exception:
|
68
|
+
if mappings_module_name != "no mapping":
|
69
|
+
logger.error(f"PATH_MAPPING was not imported: {exception}")
|
70
|
+
self.dto_mapping = {}
|
71
|
+
|
72
|
+
def __call__(self, path: str) -> Type[Dto]:
|
73
|
+
try:
|
74
|
+
return self.dto_mapping[path]
|
75
|
+
except KeyError:
|
76
|
+
logger.debug(f"No Dto mapping for {path}.")
|
77
|
+
return DefaultDto
|
78
|
+
|
79
|
+
|
52
80
|
def get_id_property_name(mappings_module_name: str) -> GetIdPropertyNameType:
|
53
81
|
return GetIdPropertyName(mappings_module_name=mappings_module_name)
|
54
82
|
|
@@ -86,11 +114,11 @@ class GetIdPropertyName:
|
|
86
114
|
|
87
115
|
|
88
116
|
@overload
|
89
|
-
def dummy_transformer(valid_id: str) -> str: ...
|
117
|
+
def dummy_transformer(valid_id: str) -> str: ... # pragma: no cover
|
90
118
|
|
91
119
|
|
92
120
|
@overload
|
93
|
-
def dummy_transformer(valid_id: int) -> int: ...
|
121
|
+
def dummy_transformer(valid_id: int) -> int: ... # pragma: no cover
|
94
122
|
|
95
123
|
|
96
124
|
def dummy_transformer(valid_id: Any) -> Any:
|
File without changes
|
@@ -234,7 +234,7 @@ class IntegerSchema(SchemaBase[int], frozen=True):
|
|
234
234
|
|
235
235
|
return randint(self._min_value, self._max_value)
|
236
236
|
|
237
|
-
def get_values_out_of_bounds(self, current_value: int) -> list[int]:
|
237
|
+
def get_values_out_of_bounds(self, current_value: int) -> list[int]: # pylint: disable=unused-argument
|
238
238
|
invalid_values: list[int] = []
|
239
239
|
|
240
240
|
if self._min_value > self._min_int:
|
@@ -333,7 +333,7 @@ class NumberSchema(SchemaBase[float], frozen=True):
|
|
333
333
|
|
334
334
|
return uniform(self._min_value, self._max_value)
|
335
335
|
|
336
|
-
def get_values_out_of_bounds(self, current_value: float) -> list[float]:
|
336
|
+
def get_values_out_of_bounds(self, current_value: float) -> list[float]: # pylint: disable=unused-argument
|
337
337
|
invalid_values: list[float] = []
|
338
338
|
|
339
339
|
if self._min_value > self._min_float:
|
@@ -643,21 +643,33 @@ class RequestBodyObject(BaseModel):
|
|
643
643
|
required: bool = False
|
644
644
|
description: str = ""
|
645
645
|
|
646
|
-
@
|
646
|
+
@cached_property
|
647
647
|
def schema_(self) -> SchemaObjectTypes | None:
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
648
|
+
if not self.mime_type:
|
649
|
+
return None
|
650
|
+
|
651
|
+
if len(self._json_schemas) > 1:
|
652
|
+
logger.info(
|
653
|
+
f"Multiple JSON media types defined for requestBody, "
|
654
|
+
f"using the first candidate from {self.content}"
|
655
|
+
)
|
656
|
+
return self._json_schemas[self.mime_type]
|
657
|
+
|
658
|
+
@cached_property
|
659
|
+
def mime_type(self) -> str | None:
|
660
|
+
if not self._json_schemas:
|
656
661
|
return None
|
657
662
|
|
658
|
-
|
659
|
-
|
660
|
-
|
663
|
+
return next(iter(self._json_schemas))
|
664
|
+
|
665
|
+
@cached_property
|
666
|
+
def _json_schemas(self) -> dict[str, SchemaObjectTypes]:
|
667
|
+
json_schemas = {
|
668
|
+
mime_type: media_type.schema_
|
669
|
+
for mime_type, media_type in self.content.items()
|
670
|
+
if "json" in mime_type and media_type.schema_ is not None
|
671
|
+
}
|
672
|
+
return json_schemas
|
661
673
|
|
662
674
|
|
663
675
|
class HeaderObject(BaseModel): ...
|
@@ -673,10 +685,6 @@ class ResponseObject(BaseModel):
|
|
673
685
|
links: dict[str, LinkObject] = {}
|
674
686
|
|
675
687
|
|
676
|
-
# class ComponentsObject(BaseModel):
|
677
|
-
# schemas: dict[str, SchemaObjectTypes]
|
678
|
-
|
679
|
-
|
680
688
|
class OperationObject(BaseModel):
|
681
689
|
operationId: str | None = None
|
682
690
|
summary: str = ""
|
@@ -1,12 +1,12 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<keywordspec name="OpenApiLibCore" type="LIBRARY" format="HTML" scope="SUITE" generated="2025-
|
3
|
-
<version>1.0.
|
2
|
+
<keywordspec name="OpenApiLibCore" type="LIBRARY" format="HTML" scope="SUITE" generated="2025-06-09T18:38:28+00:00" specversion="6" source="/workspaces/robotframework-openapitools/src/OpenApiLibCore/openapi_libcore.py" lineno="175">
|
3
|
+
<version>1.0.0b5</version>
|
4
4
|
<doc><p>Main class providing the keywords and core logic to interact with an OpenAPI server.</p>
|
5
5
|
<p>Visit the <a href="https://github.com/MarketSquare/robotframework-openapi-libcore">library page</a> for an introduction.</p></doc>
|
6
6
|
<tags>
|
7
7
|
</tags>
|
8
8
|
<inits>
|
9
|
-
<init name="__init__" lineno="
|
9
|
+
<init name="__init__" lineno="183">
|
10
10
|
<arguments repr="source: str, origin: str = , base_path: str = , response_validation: ValidationLevel = WARN, disable_server_validation: bool = True, mappings_path: str | Path = , invalid_property_default_response: int = 422, default_id_property_name: str = id, faker_locale: str | list[str] = , require_body_for_invalid_url: bool = False, recursion_limit: int = 1, recursion_default: JSON = {}, username: str = , password: str = , security_token: str = , auth: AuthBase | None = None, cert: str | tuple[str, str] = , verify_tls: bool | str = True, extra_headers: Mapping[str, str] = {}, cookies: MutableMapping[str, str] | RequestsCookieJar | None = None, proxies: MutableMapping[str, str] | None = None">
|
11
11
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="source: str">
|
12
12
|
<name>source</name>
|
@@ -208,7 +208,7 @@
|
|
208
208
|
</init>
|
209
209
|
</inits>
|
210
210
|
<keywords>
|
211
|
-
<kw name="Assert Href To Resource Is Valid" lineno="
|
211
|
+
<kw name="Assert Href To Resource Is Valid" lineno="729">
|
212
212
|
<arguments repr="href: str, referenced_resource: dict[str, JSON]">
|
213
213
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="href: str">
|
214
214
|
<name>href</name>
|
@@ -225,7 +225,7 @@
|
|
225
225
|
<doc><p>Attempt to GET the resource referenced by the <span class="name">href</span> and validate it's equal to the provided <span class="name">referenced_resource</span> object / dictionary.</p></doc>
|
226
226
|
<shortdoc>Attempt to GET the resource referenced by the `href` and validate it's equal to the provided `referenced_resource` object / dictionary.</shortdoc>
|
227
227
|
</kw>
|
228
|
-
<kw name="Authorized Request" lineno="
|
228
|
+
<kw name="Authorized Request" lineno="649">
|
229
229
|
<arguments repr="url: str, method: str, params: dict[str, Any] | None = None, headers: dict[str, str] | None = None, json_data: JSON | None = None, data: Any | None = None, files: Any | None = None">
|
230
230
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
|
231
231
|
<name>url</name>
|
@@ -288,7 +288,7 @@
|
|
288
288
|
<p>&gt; Note: provided username / password or auth objects take precedence over token based security</p></doc>
|
289
289
|
<shortdoc>Perform a request using the security token or authentication set in the library.</shortdoc>
|
290
290
|
</kw>
|
291
|
-
<kw name="Ensure In Use" lineno="
|
291
|
+
<kw name="Ensure In Use" lineno="634">
|
292
292
|
<arguments repr="url: str, resource_relation: IdReference">
|
293
293
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
|
294
294
|
<name>url</name>
|
@@ -302,7 +302,7 @@
|
|
302
302
|
<doc><p>Ensure that the (right-most) <span class="name">id</span> of the resource referenced by the <span class="name">url</span> is used by the resource defined by the <span class="name">resource_relation</span>.</p></doc>
|
303
303
|
<shortdoc>Ensure that the (right-most) `id` of the resource referenced by the `url` is used by the resource defined by the `resource_relation`.</shortdoc>
|
304
304
|
</kw>
|
305
|
-
<kw name="Get Ids From Url" lineno="
|
305
|
+
<kw name="Get Ids From Url" lineno="599">
|
306
306
|
<arguments repr="url: str">
|
307
307
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
|
308
308
|
<name>url</name>
|
@@ -315,7 +315,7 @@
|
|
315
315
|
<doc><p>Perform a GET request on the <span class="name">url</span> and return the list of resource <span class="name">ids</span> from the response.</p></doc>
|
316
316
|
<shortdoc>Perform a GET request on the `url` and return the list of resource `ids` from the response.</shortdoc>
|
317
317
|
</kw>
|
318
|
-
<kw name="Get Invalid Body Data" lineno="
|
318
|
+
<kw name="Get Invalid Body Data" lineno="496">
|
319
319
|
<arguments repr="url: str, method: str, status_code: int, request_data: RequestData">
|
320
320
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
|
321
321
|
<name>url</name>
|
@@ -342,7 +342,7 @@
|
|
342
342
|
<p>&gt; Note: applicable UniquePropertyValueConstraint and IdReference Relations are considered before changes to <span class="name">json_data</span> are made.</p></doc>
|
343
343
|
<shortdoc>Return `json_data` based on the `dto` on the `request_data` that will cause the provided `status_code` for the `method` operation on the `url`.</shortdoc>
|
344
344
|
</kw>
|
345
|
-
<kw name="Get Invalidated Parameters" lineno="
|
345
|
+
<kw name="Get Invalidated Parameters" lineno="519">
|
346
346
|
<arguments repr="status_code: int, request_data: RequestData">
|
347
347
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="status_code: int">
|
348
348
|
<name>status_code</name>
|
@@ -366,7 +366,7 @@
|
|
366
366
|
<doc><p>Returns a version of <span class="name">params, headers</span> as present on <span class="name">request_data</span> that has been modified to cause the provided <span class="name">status_code</span>.</p></doc>
|
367
367
|
<shortdoc>Returns a version of `params, headers` as present on `request_data` that has been modified to cause the provided `status_code`.</shortdoc>
|
368
368
|
</kw>
|
369
|
-
<kw name="Get Invalidated Url" lineno="
|
369
|
+
<kw name="Get Invalidated Url" lineno="609">
|
370
370
|
<arguments repr="valid_url: str, path: str = , expected_status_code: int = 404">
|
371
371
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="valid_url: str">
|
372
372
|
<name>valid_url</name>
|
@@ -385,10 +385,10 @@
|
|
385
385
|
</arguments>
|
386
386
|
<returntype name="str" typedoc="string"/>
|
387
387
|
<doc><p>Return an url with all the path parameters in the <span class="name">valid_url</span> replaced by a random UUID if no PathPropertiesConstraint is mapped for the <span class="name">"get"</span> operation on the mapped <a href="#type-Path" class="name">path</a> and <span class="name">expected_status_code</span>. If a PathPropertiesConstraint is mapped, the <span class="name">invalid_value</span> is returned.</p>
|
388
|
-
<p>Raises ValueError if the valid_url cannot be invalidated.</p></doc>
|
388
|
+
<p>Raises: ValueError if the valid_url cannot be invalidated.</p></doc>
|
389
389
|
<shortdoc>Return an url with all the path parameters in the `valid_url` replaced by a random UUID if no PathPropertiesConstraint is mapped for the `"get"` operation on the mapped `path` and `expected_status_code`. If a PathPropertiesConstraint is mapped, the `invalid_value` is returned.</shortdoc>
|
390
390
|
</kw>
|
391
|
-
<kw name="Get Json Data With Conflict" lineno="
|
391
|
+
<kw name="Get Json Data With Conflict" lineno="535">
|
392
392
|
<arguments repr="url: str, method: str, dto: Dto, conflict_status_code: int">
|
393
393
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
|
394
394
|
<name>url</name>
|
@@ -414,7 +414,7 @@
|
|
414
414
|
<doc><p>Return <span class="name">json_data</span> based on the <span class="name">UniquePropertyValueConstraint</span> that must be returned by the <span class="name">get_relations</span> implementation on the <span class="name">dto</span> for the given <span class="name">conflict_status_code</span>.</p></doc>
|
415
415
|
<shortdoc>Return `json_data` based on the `UniquePropertyValueConstraint` that must be returned by the `get_relations` implementation on the `dto` for the given `conflict_status_code`.</shortdoc>
|
416
416
|
</kw>
|
417
|
-
<kw name="Get Parameterized Path From Url" lineno="
|
417
|
+
<kw name="Get Parameterized Path From Url" lineno="585">
|
418
418
|
<arguments repr="url: str">
|
419
419
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
|
420
420
|
<name>url</name>
|
@@ -425,7 +425,7 @@
|
|
425
425
|
<doc><p>Return the path as found in the <span class="name">paths</span> section based on the given <span class="name">url</span>.</p></doc>
|
426
426
|
<shortdoc>Return the path as found in the `paths` section based on the given `url`.</shortdoc>
|
427
427
|
</kw>
|
428
|
-
<kw name="Get Request Data" lineno="
|
428
|
+
<kw name="Get Request Data" lineno="485">
|
429
429
|
<arguments repr="path: str, method: str">
|
430
430
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
|
431
431
|
<name>path</name>
|
@@ -440,7 +440,7 @@
|
|
440
440
|
<doc><p>Return an object with valid request data for body, headers and query params.</p></doc>
|
441
441
|
<shortdoc>Return an object with valid request data for body, headers and query params.</shortdoc>
|
442
442
|
</kw>
|
443
|
-
<kw name="Get Request Values" lineno="
|
443
|
+
<kw name="Get Request Values" lineno="444">
|
444
444
|
<arguments repr="path: str, method: str, overrides: Mapping[str, JSON] = {}">
|
445
445
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
|
446
446
|
<name>path</name>
|
@@ -463,7 +463,7 @@
|
|
463
463
|
<doc><p>Return an object with all (valid) request values needed to make a request.</p></doc>
|
464
464
|
<shortdoc>Return an object with all (valid) request values needed to make a request.</shortdoc>
|
465
465
|
</kw>
|
466
|
-
<kw name="Get Valid Id For Path" lineno="
|
466
|
+
<kw name="Get Valid Id For Path" lineno="573">
|
467
467
|
<arguments repr="path: str">
|
468
468
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
|
469
469
|
<name>path</name>
|
@@ -479,7 +479,7 @@
|
|
479
479
|
<p>To prevent resource conflicts with other test cases, a new resource is created (by a POST operation) if possible.</p></doc>
|
480
480
|
<shortdoc>Support keyword that returns the `id` for an existing resource at `path`.</shortdoc>
|
481
481
|
</kw>
|
482
|
-
<kw name="Get Valid Url" lineno="
|
482
|
+
<kw name="Get Valid Url" lineno="554">
|
483
483
|
<arguments repr="path: str">
|
484
484
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
|
485
485
|
<name>path</name>
|
@@ -492,7 +492,7 @@
|
|
492
492
|
<p>&gt; Note: if valid ids cannot be retrieved within the scope of the API, the <span class="name">PathPropertiesConstraint</span> Relation can be used. More information can be found <a href="https://marketsquare.github.io/robotframework-openapitools/advanced_use.html">here</a>.</p></doc>
|
493
493
|
<shortdoc>This keyword returns a valid url for the given `path`.</shortdoc>
|
494
494
|
</kw>
|
495
|
-
<kw name="Perform Validated Request" lineno="
|
495
|
+
<kw name="Perform Validated Request" lineno="698">
|
496
496
|
<arguments repr="path: str, status_code: int, request_values: RequestValues, original_data: Mapping[str, JSON] = {}">
|
497
497
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
|
498
498
|
<name>path</name>
|
@@ -518,7 +518,7 @@
|
|
518
518
|
<doc><p>This keyword first calls the Authorized Request keyword, then the Validate Response keyword and finally validates, for <span class="name">DELETE</span> operations, whether the target resource was indeed deleted (OK response) or not (error responses).</p></doc>
|
519
519
|
<shortdoc>This keyword first calls the Authorized Request keyword, then the Validate Response keyword and finally validates, for `DELETE` operations, whether the target resource was indeed deleted (OK response) or not (error responses).</shortdoc>
|
520
520
|
</kw>
|
521
|
-
<kw name="Set Auth" lineno="
|
521
|
+
<kw name="Set Auth" lineno="422">
|
522
522
|
<arguments repr="auth: AuthBase">
|
523
523
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="auth: AuthBase">
|
524
524
|
<name>auth</name>
|
@@ -529,7 +529,7 @@
|
|
529
529
|
<p>After calling this keyword, subsequent requests will use the provided <span class="name">auth</span> instance.</p></doc>
|
530
530
|
<shortdoc>Set the `auth` used for authentication after the library is imported.</shortdoc>
|
531
531
|
</kw>
|
532
|
-
<kw name="Set Basic Auth" lineno="
|
532
|
+
<kw name="Set Basic Auth" lineno="410">
|
533
533
|
<arguments repr="username: str, password: str">
|
534
534
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="username: str">
|
535
535
|
<name>username</name>
|
@@ -544,7 +544,7 @@
|
|
544
544
|
<p>After calling this keyword, subsequent requests will use the provided credentials.</p></doc>
|
545
545
|
<shortdoc>Set the `username` and `password` used for basic authentication after the library is imported.</shortdoc>
|
546
546
|
</kw>
|
547
|
-
<kw name="Set Extra Headers" lineno="
|
547
|
+
<kw name="Set Extra Headers" lineno="432">
|
548
548
|
<arguments repr="extra_headers: dict[str, str]">
|
549
549
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="extra_headers: dict[str, str]">
|
550
550
|
<name>extra_headers</name>
|
@@ -558,7 +558,7 @@
|
|
558
558
|
<p>After calling this keyword, subsequent requests will use the provided <span class="name">extra_headers</span>.</p></doc>
|
559
559
|
<shortdoc>Set the `extra_headers` used in requests after the library is imported.</shortdoc>
|
560
560
|
</kw>
|
561
|
-
<kw name="Set Origin" lineno="
|
561
|
+
<kw name="Set Origin" lineno="387">
|
562
562
|
<arguments repr="origin: str">
|
563
563
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="origin: str">
|
564
564
|
<name>origin</name>
|
@@ -570,7 +570,7 @@
|
|
570
570
|
<p>In combination with OpenApiLibCore, the <span class="name">origin</span> can be used at any point to target another server that hosts an API that complies to the same OAS.</p></doc>
|
571
571
|
<shortdoc>Set the `origin` after the library is imported.</shortdoc>
|
572
572
|
</kw>
|
573
|
-
<kw name="Set Security Token" lineno="
|
573
|
+
<kw name="Set Security Token" lineno="401">
|
574
574
|
<arguments repr="security_token: str">
|
575
575
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="security_token: str">
|
576
576
|
<name>security_token</name>
|
@@ -581,7 +581,7 @@
|
|
581
581
|
<p>After calling this keyword, subsequent requests will use the provided token.</p></doc>
|
582
582
|
<shortdoc>Set the `security_token` after the library is imported.</shortdoc>
|
583
583
|
</kw>
|
584
|
-
<kw name="Validate Response" lineno="
|
584
|
+
<kw name="Validate Response" lineno="744">
|
585
585
|
<arguments repr="path: str, response: Response, original_data: Mapping[str, JSON] = {}">
|
586
586
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
|
587
587
|
<name>path</name>
|
@@ -611,7 +611,7 @@
|
|
611
611
|
</ul></doc>
|
612
612
|
<shortdoc>Validate the `response` by performing the following validations: - validate the `response` against the openapi schema for the `path` - validate that the response does not contain extra properties - validate that a href, if present, refers to the correct resource - validate that the value for a property that is in the response is equal to the property value that was send - validate that no `original_data` is preserved when performing a PUT operation - validate that a PATCH operation only updates the provided properties</shortdoc>
|
613
613
|
</kw>
|
614
|
-
<kw name="Validate Response Using Validator" lineno="
|
614
|
+
<kw name="Validate Response Using Validator" lineno="718">
|
615
615
|
<arguments repr="response: Response">
|
616
616
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="response: Response">
|
617
617
|
<name>response</name>
|
@@ -621,7 +621,7 @@
|
|
621
621
|
<doc><p>Validate the <span class="name">response</span> against the OpenAPI Spec that is loaded during library initialization.</p></doc>
|
622
622
|
<shortdoc>Validate the `response` against the OpenAPI Spec that is loaded during library initialization.</shortdoc>
|
623
623
|
</kw>
|
624
|
-
<kw name="Validate Send Response" lineno="
|
624
|
+
<kw name="Validate Send Response" lineno="774">
|
625
625
|
<arguments repr="response: Response, original_data: Mapping[str, JSON] = {}">
|
626
626
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="response: Response">
|
627
627
|
<name>response</name>
|
@@ -140,17 +140,18 @@ from robot.api.exceptions import FatalError
|
|
140
140
|
from robot.libraries.BuiltIn import BuiltIn
|
141
141
|
|
142
142
|
import OpenApiLibCore.data_generation as _data_generation
|
143
|
-
import OpenApiLibCore.data_invalidation as
|
144
|
-
import OpenApiLibCore.path_functions as
|
145
|
-
import OpenApiLibCore.path_invalidation as
|
146
|
-
import OpenApiLibCore.resource_relations as
|
147
|
-
import OpenApiLibCore.validation as
|
143
|
+
import OpenApiLibCore.data_invalidation as _data_invalidation
|
144
|
+
import OpenApiLibCore.path_functions as _path_functions
|
145
|
+
import OpenApiLibCore.path_invalidation as _path_invalidation
|
146
|
+
import OpenApiLibCore.resource_relations as _resource_relations
|
147
|
+
import OpenApiLibCore.validation as _validation
|
148
148
|
from OpenApiLibCore.annotations import JSON
|
149
149
|
from OpenApiLibCore.dto_base import Dto, IdReference
|
150
150
|
from OpenApiLibCore.dto_utils import (
|
151
151
|
DEFAULT_ID_PROPERTY_NAME,
|
152
152
|
get_dto_class,
|
153
153
|
get_id_property_name,
|
154
|
+
get_path_dto_class,
|
154
155
|
)
|
155
156
|
from OpenApiLibCore.localized_faker import FAKE
|
156
157
|
from OpenApiLibCore.models import (
|
@@ -184,7 +185,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
184
185
|
source: str,
|
185
186
|
origin: str = "",
|
186
187
|
base_path: str = "",
|
187
|
-
response_validation:
|
188
|
+
response_validation: _validation.ValidationLevel = _validation.ValidationLevel.WARN,
|
188
189
|
disable_server_validation: bool = True,
|
189
190
|
mappings_path: str | Path = "",
|
190
191
|
invalid_property_default_response: int = 422,
|
@@ -359,12 +360,18 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
359
360
|
self.get_dto_class = get_dto_class(
|
360
361
|
mappings_module_name=mappings_module_name
|
361
362
|
)
|
363
|
+
self.get_path_dto_class = get_path_dto_class(
|
364
|
+
mappings_module_name=mappings_module_name
|
365
|
+
)
|
362
366
|
self.get_id_property_name = get_id_property_name(
|
363
367
|
mappings_module_name=mappings_module_name
|
364
368
|
)
|
365
369
|
sys.path.pop()
|
366
370
|
else:
|
367
371
|
self.get_dto_class = get_dto_class(mappings_module_name="no mapping")
|
372
|
+
self.get_path_dto_class = get_path_dto_class(
|
373
|
+
mappings_module_name="no mapping"
|
374
|
+
)
|
368
375
|
self.get_id_property_name = get_id_property_name(
|
369
376
|
mappings_module_name="no mapping"
|
370
377
|
)
|
@@ -500,7 +507,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
500
507
|
> Note: applicable UniquePropertyValueConstraint and IdReference Relations are
|
501
508
|
considered before changes to `json_data` are made.
|
502
509
|
"""
|
503
|
-
return
|
510
|
+
return _data_invalidation.get_invalid_body_data(
|
504
511
|
url=url,
|
505
512
|
method=method,
|
506
513
|
status_code=status_code,
|
@@ -518,7 +525,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
518
525
|
Returns a version of `params, headers` as present on `request_data` that has
|
519
526
|
been modified to cause the provided `status_code`.
|
520
527
|
"""
|
521
|
-
return
|
528
|
+
return _data_invalidation.get_invalidated_parameters(
|
522
529
|
status_code=status_code,
|
523
530
|
request_data=request_data,
|
524
531
|
invalid_property_default_response=self.invalid_property_default_response,
|
@@ -533,7 +540,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
533
540
|
returned by the `get_relations` implementation on the `dto` for the given
|
534
541
|
`conflict_status_code`.
|
535
542
|
"""
|
536
|
-
return
|
543
|
+
return _data_invalidation.get_json_data_with_conflict(
|
537
544
|
url=url,
|
538
545
|
base_url=self.base_url,
|
539
546
|
method=method,
|
@@ -555,10 +562,10 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
555
562
|
`PathPropertiesConstraint` Relation can be used. More information can be found
|
556
563
|
[https://marketsquare.github.io/robotframework-openapitools/advanced_use.html | here].
|
557
564
|
"""
|
558
|
-
return
|
565
|
+
return _path_functions.get_valid_url(
|
559
566
|
path=path,
|
560
567
|
base_url=self.base_url,
|
561
|
-
|
568
|
+
get_path_dto_class=self.get_path_dto_class,
|
562
569
|
openapi_spec=self.openapi_spec,
|
563
570
|
)
|
564
571
|
|
@@ -570,7 +577,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
570
577
|
To prevent resource conflicts with other test cases, a new resource is created
|
571
578
|
(by a POST operation) if possible.
|
572
579
|
"""
|
573
|
-
return
|
580
|
+
return _path_functions.get_valid_id_for_path(
|
574
581
|
path=path, get_id_property_name=self.get_id_property_name
|
575
582
|
)
|
576
583
|
|
@@ -583,7 +590,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
583
590
|
path_parts = path.split("/")
|
584
591
|
# first part will be '' since a path starts with /
|
585
592
|
path_parts.pop(0)
|
586
|
-
parameterized_path =
|
593
|
+
parameterized_path = _path_functions.get_parametrized_path(
|
587
594
|
path=path, openapi_spec=self.openapi_spec
|
588
595
|
)
|
589
596
|
return parameterized_path
|
@@ -594,7 +601,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
594
601
|
Perform a GET request on the `url` and return the list of resource
|
595
602
|
`ids` from the response.
|
596
603
|
"""
|
597
|
-
return
|
604
|
+
return _path_functions.get_ids_from_url(
|
598
605
|
url=url, get_id_property_name=self.get_id_property_name
|
599
606
|
)
|
600
607
|
|
@@ -611,13 +618,13 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
611
618
|
on the mapped `path` and `expected_status_code`.
|
612
619
|
If a PathPropertiesConstraint is mapped, the `invalid_value` is returned.
|
613
620
|
|
614
|
-
Raises ValueError if the valid_url cannot be invalidated.
|
621
|
+
Raises: ValueError if the valid_url cannot be invalidated.
|
615
622
|
"""
|
616
|
-
return
|
623
|
+
return _path_invalidation.get_invalidated_url(
|
617
624
|
valid_url=valid_url,
|
618
625
|
path=path,
|
619
626
|
base_url=self.base_url,
|
620
|
-
|
627
|
+
get_path_dto_class=self.get_path_dto_class,
|
621
628
|
expected_status_code=expected_status_code,
|
622
629
|
)
|
623
630
|
|
@@ -629,7 +636,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
629
636
|
Ensure that the (right-most) `id` of the resource referenced by the `url`
|
630
637
|
is used by the resource defined by the `resource_relation`.
|
631
638
|
"""
|
632
|
-
|
639
|
+
_resource_relations.ensure_in_use(
|
633
640
|
url=url,
|
634
641
|
base_url=self.base_url,
|
635
642
|
openapi_spec=self.openapi_spec,
|
@@ -700,7 +707,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
700
707
|
Response keyword and finally validates, for `DELETE` operations, whether
|
701
708
|
the target resource was indeed deleted (OK response) or not (error responses).
|
702
709
|
"""
|
703
|
-
|
710
|
+
_validation.perform_validated_request(
|
704
711
|
path=path,
|
705
712
|
status_code=status_code,
|
706
713
|
request_values=request_values,
|
@@ -713,7 +720,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
713
720
|
Validate the `response` against the OpenAPI Spec that is
|
714
721
|
loaded during library initialization.
|
715
722
|
"""
|
716
|
-
|
723
|
+
_validation.validate_response_using_validator(
|
717
724
|
response=response,
|
718
725
|
response_validator=self.response_validator,
|
719
726
|
)
|
@@ -726,7 +733,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
726
733
|
Attempt to GET the resource referenced by the `href` and validate it's equal
|
727
734
|
to the provided `referenced_resource` object / dictionary.
|
728
735
|
"""
|
729
|
-
|
736
|
+
_validation.assert_href_to_resource_is_valid(
|
730
737
|
href=href,
|
731
738
|
origin=self.origin,
|
732
739
|
base_url=self.base_url,
|
@@ -750,7 +757,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
750
757
|
- validate that no `original_data` is preserved when performing a PUT operation
|
751
758
|
- validate that a PATCH operation only updates the provided properties
|
752
759
|
"""
|
753
|
-
|
760
|
+
_validation.validate_response(
|
754
761
|
path=path,
|
755
762
|
response=response,
|
756
763
|
response_validator=self.response_validator,
|
@@ -774,7 +781,9 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
774
781
|
In case a PATCH request, validate that only the properties that were patched
|
775
782
|
have changed and that other properties are still at their pre-patch values.
|
776
783
|
"""
|
777
|
-
|
784
|
+
_validation.validate_send_response(
|
785
|
+
response=response, original_data=original_data
|
786
|
+
)
|
778
787
|
|
779
788
|
# endregion
|
780
789
|
|
@@ -844,7 +853,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
844
853
|
refstring: str, # pylint: disable=unused-argument
|
845
854
|
recursions: JSON, # pylint: disable=unused-argument
|
846
855
|
) -> JSON:
|
847
|
-
return self._recursion_default
|
856
|
+
return self._recursion_default # pragma: no cover
|
848
857
|
|
849
858
|
try:
|
850
859
|
# Since parsing of the OAS and creating the Spec can take a long time,
|
@@ -892,11 +901,11 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
|
|
892
901
|
|
893
902
|
return parser, validation_spec, response_validator
|
894
903
|
|
895
|
-
except ResolutionError as exception:
|
904
|
+
except ResolutionError as exception: # pragma: no cover
|
896
905
|
raise FatalError(
|
897
906
|
f"ResolutionError while trying to load openapi spec: {exception}"
|
898
907
|
) from exception
|
899
|
-
except ValidationError as exception:
|
908
|
+
except ValidationError as exception: # pragma: no cover
|
900
909
|
raise FatalError(
|
901
910
|
f"ValidationError while trying to load openapi spec: {exception}"
|
902
911
|
) from exception
|
@@ -24,7 +24,7 @@ def get_safe_name_for_oas_name(oas_name: str) -> str:
|
|
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,7 +36,7 @@ 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
|
@@ -46,7 +46,7 @@ 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
|
|
@@ -8,9 +8,8 @@ from typing import Any
|
|
8
8
|
from requests import Response
|
9
9
|
from robot.libraries.BuiltIn import BuiltIn
|
10
10
|
|
11
|
-
from OpenApiLibCore.dto_base import PathPropertiesConstraint
|
12
11
|
from OpenApiLibCore.models import OpenApiObject
|
13
|
-
from OpenApiLibCore.protocols import
|
12
|
+
from OpenApiLibCore.protocols import GetIdPropertyNameType, GetPathDtoClassType
|
14
13
|
from OpenApiLibCore.request_data import RequestData
|
15
14
|
|
16
15
|
run_keyword = BuiltIn().run_keyword
|
@@ -64,7 +63,7 @@ def get_parametrized_path(path: str, openapi_spec: OpenApiObject) -> str:
|
|
64
63
|
def get_valid_url(
|
65
64
|
path: str,
|
66
65
|
base_url: str,
|
67
|
-
|
66
|
+
get_path_dto_class: GetPathDtoClassType,
|
68
67
|
openapi_spec: OpenApiObject,
|
69
68
|
) -> str:
|
70
69
|
try:
|
@@ -75,9 +74,9 @@ def get_valid_url(
|
|
75
74
|
raise ValueError(
|
76
75
|
f"{path} not found in paths section of the OpenAPI document."
|
77
76
|
) from None
|
78
|
-
dto_class =
|
79
|
-
relations = dto_class.
|
80
|
-
paths = [p.path for p in relations
|
77
|
+
dto_class = get_path_dto_class(path=path)
|
78
|
+
relations = dto_class.get_path_relations()
|
79
|
+
paths = [p.path for p in relations]
|
81
80
|
if paths:
|
82
81
|
url = f"{base_url}{choice(paths)}"
|
83
82
|
return url
|
@@ -5,8 +5,7 @@ from uuid import uuid4
|
|
5
5
|
|
6
6
|
from robot.libraries.BuiltIn import BuiltIn
|
7
7
|
|
8
|
-
from OpenApiLibCore.
|
9
|
-
from OpenApiLibCore.protocols import GetDtoClassType
|
8
|
+
from OpenApiLibCore.protocols import GetPathDtoClassType
|
10
9
|
|
11
10
|
run_keyword = BuiltIn().run_keyword
|
12
11
|
|
@@ -15,16 +14,15 @@ def get_invalidated_url(
|
|
15
14
|
valid_url: str,
|
16
15
|
path: str,
|
17
16
|
base_url: str,
|
18
|
-
|
17
|
+
get_path_dto_class: GetPathDtoClassType,
|
19
18
|
expected_status_code: int,
|
20
19
|
) -> str:
|
21
|
-
dto_class =
|
22
|
-
relations = dto_class.
|
20
|
+
dto_class = get_path_dto_class(path=path)
|
21
|
+
relations = dto_class.get_path_relations()
|
23
22
|
paths = [
|
24
23
|
p.invalid_value
|
25
24
|
for p in relations
|
26
|
-
if
|
27
|
-
and p.invalid_value_error_code == expected_status_code
|
25
|
+
if p.invalid_value_error_code == expected_status_code
|
28
26
|
]
|
29
27
|
if paths:
|
30
28
|
url = f"{base_url}{choice(paths)}"
|
@@ -30,3 +30,9 @@ class GetIdPropertyNameType(Protocol):
|
|
30
30
|
) -> tuple[
|
31
31
|
str, Callable[[str], str] | Callable[[int], int]
|
32
32
|
]: ... # pragma: no cover
|
33
|
+
|
34
|
+
|
35
|
+
class GetPathDtoClassType(Protocol):
|
36
|
+
def __init__(self, mappings_module_name: str) -> None: ... # pragma: no cover
|
37
|
+
|
38
|
+
def __call__(self, path: str) -> Type[Dto]: ... # pragma: no cover
|
File without changes
|
@@ -4,7 +4,7 @@ from requests import Response
|
|
4
4
|
from robot.api import logger
|
5
5
|
from robot.libraries.BuiltIn import BuiltIn
|
6
6
|
|
7
|
-
import OpenApiLibCore.path_functions as
|
7
|
+
import OpenApiLibCore.path_functions as _path_functions
|
8
8
|
from OpenApiLibCore.dto_base import IdReference
|
9
9
|
from OpenApiLibCore.models import OpenApiObject
|
10
10
|
from OpenApiLibCore.request_data import RequestData
|
@@ -22,7 +22,9 @@ def ensure_in_use(
|
|
22
22
|
|
23
23
|
path = url.replace(base_url, "")
|
24
24
|
path_parts = path.split("/")
|
25
|
-
parameterized_path =
|
25
|
+
parameterized_path = _path_functions.get_parametrized_path(
|
26
|
+
path=path, openapi_spec=openapi_spec
|
27
|
+
)
|
26
28
|
parameterized_path_parts = parameterized_path.split("/")
|
27
29
|
for part, param_part in zip(
|
28
30
|
reversed(path_parts), reversed(parameterized_path_parts)
|
@@ -75,7 +75,7 @@ def perform_validated_request(
|
|
75
75
|
f"\nGot: {_json.dumps(response_json, indent=4, sort_keys=True)}"
|
76
76
|
)
|
77
77
|
raise AssertionError(
|
78
|
-
f"Response status_code {response.status_code} was not {status_code}"
|
78
|
+
f"Response status_code {response.status_code} was not {status_code}."
|
79
79
|
)
|
80
80
|
|
81
81
|
run_keyword("validate_response", path, response, original_data)
|
@@ -90,7 +90,7 @@ def perform_validated_request(
|
|
90
90
|
if response.ok:
|
91
91
|
if get_response.ok:
|
92
92
|
raise AssertionError(
|
93
|
-
f"Resource still exists after deletion. Url was {request_values.url}"
|
93
|
+
f"Resource still exists after deletion. Url was {request_values.url}."
|
94
94
|
)
|
95
95
|
# if the path supports GET, 404 is expected, if not 405 is expected
|
96
96
|
if get_response.status_code not in [404, 405]:
|
@@ -102,7 +102,7 @@ def perform_validated_request(
|
|
102
102
|
elif not get_response.ok:
|
103
103
|
raise AssertionError(
|
104
104
|
f"Resource could not be retrieved after failed deletion. "
|
105
|
-
f"Url was {request_values.url}, status_code was {get_response.status_code}"
|
105
|
+
f"Url was {request_values.url}, status_code was {get_response.status_code}."
|
106
106
|
)
|
107
107
|
|
108
108
|
|
@@ -279,11 +279,6 @@ def validate_send_response(
|
|
279
279
|
)
|
280
280
|
return None
|
281
281
|
|
282
|
-
# FIXME: this applies to removed code
|
283
|
-
# POST on /resource_type/{id}/array_item/ will return the updated {id} resource
|
284
|
-
# instead of a newly created resource. In this case, the send_json must be
|
285
|
-
# in the array of the 'array_item' property on {id}
|
286
|
-
|
287
282
|
send_path: str = response.request.path_url
|
288
283
|
response_path = response_data.get("href", None)
|
289
284
|
if response_path and send_path not in response_path:
|
@@ -380,6 +375,6 @@ def _get_response_object(
|
|
380
375
|
path_operations = path_item.get_operations()
|
381
376
|
operation_data = path_operations.get(method)
|
382
377
|
if operation_data is None:
|
383
|
-
raise ValueError(f"method '{method}' not supported for {path}")
|
378
|
+
raise ValueError(f"method '{method}' not supported for {path}.")
|
384
379
|
|
385
380
|
return operation_data.responses[status]
|
@@ -98,7 +98,7 @@ def get_invalid_value(
|
|
98
98
|
# Violate min / max values or length if possible
|
99
99
|
try:
|
100
100
|
values_out_of_bounds = value_schema.get_values_out_of_bounds(
|
101
|
-
current_value=current_value
|
101
|
+
current_value=current_value # type: ignore[arg-type]
|
102
102
|
)
|
103
103
|
invalid_values += values_out_of_bounds
|
104
104
|
except ValueError:
|
@@ -18,7 +18,7 @@ def load_openapi_spec(
|
|
18
18
|
def recursion_limit_handler(
|
19
19
|
limit: int, refstring: str, recursions: object
|
20
20
|
) -> object: # pylint: disable=unused-argument
|
21
|
-
return recursion_default
|
21
|
+
return recursion_default # pragma: no cover
|
22
22
|
|
23
23
|
parser = ResolvingParser(
|
24
24
|
source,
|
@@ -69,7 +69,7 @@ def generate(
|
|
69
69
|
return f"Generated {library_name} at {output_folder.resolve().as_posix()}/{module_name}"
|
70
70
|
|
71
71
|
|
72
|
-
if __name__ == "__main__":
|
72
|
+
if __name__ == "__main__": # pragma: no cover
|
73
73
|
source = sys.argv[1]
|
74
74
|
destination = Path(sys.argv[2])
|
75
75
|
library_name = sys.argv[3]
|
@@ -8,16 +8,20 @@ def remove_unsafe_characters_from_string(string: str) -> str:
|
|
8
8
|
string_iterator = iter(string)
|
9
9
|
capitalize_next_character = False
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
# The first character must be A-z or _
|
12
|
+
first_character = next(string_iterator, "_")
|
13
|
+
if first_character.isalpha() or first_character == "_":
|
14
|
+
yield first_character
|
15
|
+
elif first_character.isnumeric():
|
16
|
+
yield "_" + first_character
|
15
17
|
|
16
18
|
for character in string_iterator:
|
17
19
|
if character.isalnum():
|
18
20
|
if capitalize_next_character:
|
19
21
|
capitalize_next_character = False
|
20
|
-
|
22
|
+
yield character.upper()
|
23
|
+
else:
|
24
|
+
yield character
|
21
25
|
|
22
26
|
elif not capitalize_next_character:
|
23
27
|
capitalize_next_character = True
|
@@ -79,7 +79,7 @@ def get_keyword_signature(operation_details: OperationDetails) -> str:
|
|
79
79
|
else:
|
80
80
|
keyword_name = remove_unsafe_characters_from_string(
|
81
81
|
f"{operation_details.method}_{operation_details.path}"
|
82
|
-
)
|
82
|
+
).lower()
|
83
83
|
|
84
84
|
parameters = operation_details.parameters or []
|
85
85
|
path_parameters = [p for p in parameters if p.in_ == "path" and p.schema_]
|
@@ -93,7 +93,7 @@ def get_keyword_signature(operation_details: OperationDetails) -> str:
|
|
93
93
|
keyword_argument_names: set[str] = set()
|
94
94
|
|
95
95
|
for parameter in path_parameters:
|
96
|
-
annotation = f"{parameter.schema_.annotation_string} = UNSET"
|
96
|
+
annotation = f"{parameter.schema_.annotation_string} = UNSET" # type: ignore[union-attr]
|
97
97
|
safe_name = get_safe_name_for_oas_name(parameter.name)
|
98
98
|
keyword_argument_names.add(safe_name)
|
99
99
|
argument = f", {safe_name}: {annotation}"
|
@@ -120,7 +120,7 @@ def get_keyword_signature(operation_details: OperationDetails) -> str:
|
|
120
120
|
arguments += argument
|
121
121
|
|
122
122
|
for parameter in query_parameters:
|
123
|
-
annotation = f"{parameter.schema_.annotation_string} = UNSET"
|
123
|
+
annotation = f"{parameter.schema_.annotation_string} = UNSET" # type: ignore[union-attr]
|
124
124
|
safe_name = get_safe_name_for_oas_name(parameter.name)
|
125
125
|
if safe_name in keyword_argument_names:
|
126
126
|
safe_name = "query_" + safe_name
|
@@ -129,7 +129,7 @@ def get_keyword_signature(operation_details: OperationDetails) -> str:
|
|
129
129
|
arguments += argument
|
130
130
|
|
131
131
|
for parameter in header_parameters:
|
132
|
-
annotation = f"{parameter.schema_.annotation_string} = UNSET"
|
132
|
+
annotation = f"{parameter.schema_.annotation_string} = UNSET" # type: ignore[union-attr]
|
133
133
|
safe_name = get_safe_name_for_oas_name(parameter.name)
|
134
134
|
if safe_name in keyword_argument_names:
|
135
135
|
safe_name = "header_" + safe_name
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|