robotframework-openapitools 0.4.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- OpenApiDriver/__init__.py +45 -41
- OpenApiDriver/openapi_executors.py +78 -49
- OpenApiDriver/openapi_reader.py +114 -116
- OpenApiDriver/openapidriver.libspec +209 -133
- OpenApiDriver/openapidriver.py +31 -296
- OpenApiLibCore/__init__.py +39 -13
- OpenApiLibCore/annotations.py +10 -0
- OpenApiLibCore/data_generation/__init__.py +10 -0
- OpenApiLibCore/data_generation/body_data_generation.py +250 -0
- OpenApiLibCore/data_generation/data_generation_core.py +233 -0
- OpenApiLibCore/data_invalidation.py +294 -0
- OpenApiLibCore/dto_base.py +67 -130
- OpenApiLibCore/dto_utils.py +125 -85
- OpenApiLibCore/localized_faker.py +88 -0
- OpenApiLibCore/models.py +723 -0
- OpenApiLibCore/oas_cache.py +14 -13
- OpenApiLibCore/openapi_libcore.libspec +355 -330
- OpenApiLibCore/openapi_libcore.py +385 -1953
- OpenApiLibCore/parameter_utils.py +97 -0
- OpenApiLibCore/path_functions.py +215 -0
- OpenApiLibCore/path_invalidation.py +42 -0
- OpenApiLibCore/protocols.py +38 -0
- OpenApiLibCore/request_data.py +246 -0
- OpenApiLibCore/resource_relations.py +55 -0
- OpenApiLibCore/validation.py +380 -0
- OpenApiLibCore/value_utils.py +216 -481
- openapi_libgen/__init__.py +3 -0
- openapi_libgen/command_line.py +75 -0
- openapi_libgen/generator.py +82 -0
- openapi_libgen/parsing_utils.py +30 -0
- openapi_libgen/spec_parser.py +154 -0
- openapi_libgen/templates/__init__.jinja +3 -0
- openapi_libgen/templates/library.jinja +30 -0
- robotframework_openapitools-1.0.0.dist-info/METADATA +249 -0
- robotframework_openapitools-1.0.0.dist-info/RECORD +40 -0
- {robotframework_openapitools-0.4.0.dist-info → robotframework_openapitools-1.0.0.dist-info}/WHEEL +1 -1
- robotframework_openapitools-1.0.0.dist-info/entry_points.txt +3 -0
- roboswag/__init__.py +0 -9
- roboswag/__main__.py +0 -3
- roboswag/auth.py +0 -44
- roboswag/cli.py +0 -80
- roboswag/core.py +0 -85
- roboswag/generate/__init__.py +0 -1
- roboswag/generate/generate.py +0 -121
- roboswag/generate/models/__init__.py +0 -0
- roboswag/generate/models/api.py +0 -219
- roboswag/generate/models/definition.py +0 -28
- roboswag/generate/models/endpoint.py +0 -68
- roboswag/generate/models/parameter.py +0 -25
- roboswag/generate/models/response.py +0 -8
- roboswag/generate/models/tag.py +0 -16
- roboswag/generate/models/utils.py +0 -60
- roboswag/generate/templates/api_init.jinja +0 -15
- roboswag/generate/templates/models.jinja +0 -7
- roboswag/generate/templates/paths.jinja +0 -68
- roboswag/logger.py +0 -33
- roboswag/validate/__init__.py +0 -6
- roboswag/validate/core.py +0 -3
- roboswag/validate/schema.py +0 -21
- roboswag/validate/text_response.py +0 -14
- robotframework_openapitools-0.4.0.dist-info/METADATA +0 -42
- robotframework_openapitools-0.4.0.dist-info/RECORD +0 -41
- {robotframework_openapitools-0.4.0.dist-info → robotframework_openapitools-1.0.0.dist-info}/LICENSE +0 -0
OpenApiDriver/__init__.py
CHANGED
@@ -1,41 +1,45 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
The
|
4
|
-
|
5
|
-
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
from
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
"
|
36
|
-
"
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
41
|
-
|
1
|
+
# pylint: disable=invalid-name
|
2
|
+
"""
|
3
|
+
The OpenApiDriver package is intended to be used as a Robot Framework library.
|
4
|
+
The following classes and constants are exposed to be used by the library user:
|
5
|
+
- OpenApiDriver: The class to be used as a Library in the *** Settings *** section
|
6
|
+
- IdDependency, IdReference, PathPropertiesConstraint, PropertyValueConstraint,
|
7
|
+
UniquePropertyValueConstraint: Classes to be subclassed by the library user
|
8
|
+
when implementing a custom mapping module (advanced use).
|
9
|
+
- Dto, Relation: Base classes that can be used for type annotations.
|
10
|
+
- IGNORE: A special constant that can be used as a value in the PropertyValueConstraint.
|
11
|
+
"""
|
12
|
+
|
13
|
+
from importlib.metadata import version
|
14
|
+
|
15
|
+
from OpenApiDriver.openapidriver import OpenApiDriver
|
16
|
+
from OpenApiLibCore.dto_base import (
|
17
|
+
Dto,
|
18
|
+
IdDependency,
|
19
|
+
IdReference,
|
20
|
+
PathPropertiesConstraint,
|
21
|
+
PropertyValueConstraint,
|
22
|
+
ResourceRelation,
|
23
|
+
UniquePropertyValueConstraint,
|
24
|
+
)
|
25
|
+
from OpenApiLibCore.validation import ValidationLevel
|
26
|
+
from OpenApiLibCore.value_utils import IGNORE
|
27
|
+
|
28
|
+
try:
|
29
|
+
__version__ = version("robotframework-openapidriver")
|
30
|
+
except Exception: # pragma: no cover pylint: disable=broad-exception-caught
|
31
|
+
pass
|
32
|
+
|
33
|
+
|
34
|
+
__all__ = [
|
35
|
+
"IGNORE",
|
36
|
+
"Dto",
|
37
|
+
"IdDependency",
|
38
|
+
"IdReference",
|
39
|
+
"OpenApiDriver",
|
40
|
+
"PathPropertiesConstraint",
|
41
|
+
"PropertyValueConstraint",
|
42
|
+
"ResourceRelation",
|
43
|
+
"UniquePropertyValueConstraint",
|
44
|
+
"ValidationLevel",
|
45
|
+
]
|
@@ -1,52 +1,70 @@
|
|
1
1
|
"""Module containing the classes to perform automatic OpenAPI contract validation."""
|
2
2
|
|
3
|
-
from
|
3
|
+
from collections.abc import Mapping, MutableMapping
|
4
|
+
from http import HTTPStatus
|
5
|
+
from os import getenv
|
4
6
|
from pathlib import Path
|
5
7
|
from random import choice
|
6
|
-
from
|
8
|
+
from types import MappingProxyType
|
7
9
|
|
8
10
|
from requests import Response
|
9
11
|
from requests.auth import AuthBase
|
10
12
|
from requests.cookies import RequestsCookieJar as CookieJar
|
11
|
-
from robot.api import
|
13
|
+
from robot.api import logger
|
12
14
|
from robot.api.deco import keyword, library
|
15
|
+
from robot.api.exceptions import SkipExecution
|
13
16
|
from robot.libraries.BuiltIn import BuiltIn
|
14
17
|
|
15
|
-
from OpenApiLibCore import
|
18
|
+
from OpenApiLibCore import (
|
19
|
+
KEYWORD_NAMES as LIBCORE_KEYWORD_NAMES,
|
20
|
+
)
|
21
|
+
from OpenApiLibCore import (
|
22
|
+
OpenApiLibCore,
|
23
|
+
RequestData,
|
24
|
+
RequestValues,
|
25
|
+
ValidationLevel,
|
26
|
+
)
|
27
|
+
from OpenApiLibCore.annotations import JSON
|
16
28
|
|
17
29
|
run_keyword = BuiltIn().run_keyword
|
30
|
+
default_str_mapping: Mapping[str, str] = MappingProxyType({})
|
18
31
|
|
19
32
|
|
20
|
-
|
33
|
+
KEYWORD_NAMES = [
|
34
|
+
"test_unauthorized",
|
35
|
+
"test_forbidden",
|
36
|
+
"test_invalid_url",
|
37
|
+
"test_endpoint",
|
38
|
+
]
|
21
39
|
|
22
40
|
|
23
41
|
@library(scope="SUITE", doc_format="ROBOT")
|
24
|
-
class OpenApiExecutors(OpenApiLibCore):
|
42
|
+
class OpenApiExecutors(OpenApiLibCore):
|
25
43
|
"""Main class providing the keywords and core logic to perform endpoint validations."""
|
26
44
|
|
27
|
-
def __init__( # pylint: disable=
|
45
|
+
def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
|
28
46
|
self,
|
29
47
|
source: str,
|
30
48
|
origin: str = "",
|
31
49
|
base_path: str = "",
|
32
50
|
response_validation: ValidationLevel = ValidationLevel.WARN,
|
33
51
|
disable_server_validation: bool = True,
|
34
|
-
mappings_path:
|
52
|
+
mappings_path: str | Path = "",
|
35
53
|
invalid_property_default_response: int = 422,
|
36
54
|
default_id_property_name: str = "id",
|
37
|
-
faker_locale:
|
55
|
+
faker_locale: str | list[str] = "",
|
38
56
|
require_body_for_invalid_url: bool = False,
|
39
57
|
recursion_limit: int = 1,
|
40
|
-
recursion_default:
|
58
|
+
recursion_default: JSON = {},
|
41
59
|
username: str = "",
|
42
60
|
password: str = "",
|
43
61
|
security_token: str = "",
|
44
|
-
auth:
|
45
|
-
cert:
|
46
|
-
verify_tls:
|
47
|
-
extra_headers:
|
48
|
-
cookies:
|
49
|
-
proxies:
|
62
|
+
auth: AuthBase | None = None,
|
63
|
+
cert: str | tuple[str, str] = "",
|
64
|
+
verify_tls: bool | str = True,
|
65
|
+
extra_headers: Mapping[str, str] = default_str_mapping,
|
66
|
+
cookies: MutableMapping[str, str] | CookieJar | None = None,
|
67
|
+
proxies: MutableMapping[str, str] | None = None,
|
50
68
|
) -> None:
|
51
69
|
super().__init__(
|
52
70
|
source=source,
|
@@ -84,13 +102,13 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
84
102
|
> Note: No headers or (json) body are send with the request. For security
|
85
103
|
reasons, the authorization validation should be checked first.
|
86
104
|
"""
|
87
|
-
url: str = run_keyword("get_valid_url", path
|
105
|
+
url: str = run_keyword("get_valid_url", path)
|
88
106
|
response = self.session.request(
|
89
107
|
method=method,
|
90
108
|
url=url,
|
91
109
|
verify=False,
|
92
110
|
)
|
93
|
-
if response.status_code !=
|
111
|
+
if response.status_code != int(HTTPStatus.UNAUTHORIZED):
|
94
112
|
raise AssertionError(f"Response {response.status_code} was not 401.")
|
95
113
|
|
96
114
|
@keyword
|
@@ -105,9 +123,9 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
105
123
|
> Note: No headers or (json) body are send with the request. For security
|
106
124
|
reasons, the access rights validation should be checked first.
|
107
125
|
"""
|
108
|
-
url: str = run_keyword("get_valid_url", path
|
126
|
+
url: str = run_keyword("get_valid_url", path)
|
109
127
|
response: Response = run_keyword("authorized_request", url, method)
|
110
|
-
if response.status_code !=
|
128
|
+
if response.status_code != int(HTTPStatus.FORBIDDEN):
|
111
129
|
raise AssertionError(f"Response {response.status_code} was not 403.")
|
112
130
|
|
113
131
|
@keyword
|
@@ -130,13 +148,17 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
130
148
|
parameters are send with the request. The `require_body_for_invalid_url`
|
131
149
|
parameter can be set to `True` if needed.
|
132
150
|
"""
|
133
|
-
valid_url: str = run_keyword("get_valid_url", path
|
151
|
+
valid_url: str = run_keyword("get_valid_url", path)
|
134
152
|
|
135
|
-
|
136
|
-
url
|
137
|
-
"get_invalidated_url", valid_url, path,
|
153
|
+
try:
|
154
|
+
url = run_keyword(
|
155
|
+
"get_invalidated_url", valid_url, path, expected_status_code
|
138
156
|
)
|
139
|
-
|
157
|
+
except Exception as exception:
|
158
|
+
message = getattr(exception, "message", "")
|
159
|
+
if not message.startswith("ValueError"):
|
160
|
+
raise exception # pragma: no cover
|
161
|
+
|
140
162
|
raise SkipExecution(
|
141
163
|
f"Path {path} does not contain resource references that "
|
142
164
|
f"can be invalidated."
|
@@ -144,7 +166,7 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
144
166
|
|
145
167
|
params, headers, json_data = None, None, None
|
146
168
|
if self.require_body_for_invalid_url:
|
147
|
-
request_data =
|
169
|
+
request_data: RequestData = run_keyword("get_request_data", path, method)
|
148
170
|
params = request_data.params
|
149
171
|
headers = request_data.headers
|
150
172
|
dto = request_data.dto
|
@@ -169,11 +191,11 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
169
191
|
The keyword calls other keywords to generate the neccesary data to perform
|
170
192
|
the desired operation and validate the response against the openapi document.
|
171
193
|
"""
|
172
|
-
json_data:
|
173
|
-
original_data =
|
194
|
+
json_data: dict[str, JSON] = {}
|
195
|
+
original_data = {}
|
174
196
|
|
175
|
-
url: str = run_keyword("get_valid_url", path
|
176
|
-
request_data: RequestData =
|
197
|
+
url: str = run_keyword("get_valid_url", path)
|
198
|
+
request_data: RequestData = run_keyword("get_request_data", path, method)
|
177
199
|
params = request_data.params
|
178
200
|
headers = request_data.headers
|
179
201
|
if request_data.has_body:
|
@@ -182,10 +204,10 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
182
204
|
if method == "PATCH":
|
183
205
|
original_data = self.get_original_data(url=url)
|
184
206
|
# in case of a status code indicating an error, ensure the error occurs
|
185
|
-
if status_code >=
|
207
|
+
if status_code >= int(HTTPStatus.BAD_REQUEST):
|
186
208
|
invalidation_keyword_data = {
|
187
|
-
"
|
188
|
-
"
|
209
|
+
"get_invalid_body_data": [
|
210
|
+
"get_invalid_body_data",
|
189
211
|
url,
|
190
212
|
method,
|
191
213
|
status_code,
|
@@ -200,13 +222,13 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
200
222
|
invalidation_keywords = []
|
201
223
|
|
202
224
|
if request_data.dto.get_body_relations_for_error_code(status_code):
|
203
|
-
invalidation_keywords.append("
|
225
|
+
invalidation_keywords.append("get_invalid_body_data")
|
204
226
|
if request_data.dto.get_parameter_relations_for_error_code(status_code):
|
205
227
|
invalidation_keywords.append("get_invalidated_parameters")
|
206
228
|
if invalidation_keywords:
|
207
229
|
if (
|
208
230
|
invalidation_keyword := choice(invalidation_keywords)
|
209
|
-
) == "
|
231
|
+
) == "get_invalid_body_data":
|
210
232
|
json_data = run_keyword(
|
211
233
|
*invalidation_keyword_data[invalidation_keyword]
|
212
234
|
)
|
@@ -224,13 +246,13 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
224
246
|
params, headers = run_keyword(
|
225
247
|
*invalidation_keyword_data["get_invalidated_parameters"]
|
226
248
|
)
|
227
|
-
if request_data.
|
249
|
+
if request_data.body_schema:
|
228
250
|
json_data = run_keyword(
|
229
|
-
*invalidation_keyword_data["
|
251
|
+
*invalidation_keyword_data["get_invalid_body_data"]
|
230
252
|
)
|
231
|
-
elif request_data.
|
253
|
+
elif request_data.body_schema:
|
232
254
|
json_data = run_keyword(
|
233
|
-
*invalidation_keyword_data["
|
255
|
+
*invalidation_keyword_data["get_invalid_body_data"]
|
234
256
|
)
|
235
257
|
else:
|
236
258
|
raise SkipExecution(
|
@@ -253,20 +275,20 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
253
275
|
),
|
254
276
|
original_data,
|
255
277
|
)
|
256
|
-
if status_code <
|
278
|
+
if status_code < int(HTTPStatus.MULTIPLE_CHOICES) and (
|
257
279
|
request_data.has_optional_properties
|
258
280
|
or request_data.has_optional_params
|
259
281
|
or request_data.has_optional_headers
|
260
282
|
):
|
261
283
|
logger.info("Performing request without optional properties and parameters")
|
262
|
-
url = run_keyword("get_valid_url", path
|
263
|
-
request_data =
|
284
|
+
url = run_keyword("get_valid_url", path)
|
285
|
+
request_data = run_keyword("get_request_data", path, method)
|
264
286
|
params = request_data.get_required_params()
|
265
287
|
headers = request_data.get_required_headers()
|
266
288
|
json_data = (
|
267
|
-
request_data.get_minimal_body_dict() if request_data.has_body else
|
289
|
+
request_data.get_minimal_body_dict() if request_data.has_body else {}
|
268
290
|
)
|
269
|
-
original_data =
|
291
|
+
original_data = {}
|
270
292
|
if method == "PATCH":
|
271
293
|
original_data = self.get_original_data(url=url)
|
272
294
|
run_keyword(
|
@@ -283,15 +305,15 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
283
305
|
original_data,
|
284
306
|
)
|
285
307
|
|
286
|
-
def get_original_data(self, url: str) ->
|
308
|
+
def get_original_data(self, url: str) -> dict[str, JSON]:
|
287
309
|
"""
|
288
310
|
Attempt to GET the current data for the given url and return it.
|
289
311
|
|
290
|
-
If the GET request fails,
|
312
|
+
If the GET request fails, an empty dict is returned.
|
291
313
|
"""
|
292
|
-
original_data =
|
293
|
-
path = self.
|
294
|
-
get_request_data =
|
314
|
+
original_data = {}
|
315
|
+
path = self.get_parameterized_path_from_url(url)
|
316
|
+
get_request_data: RequestData = run_keyword("get_request_data", path, "GET")
|
295
317
|
get_params = get_request_data.params
|
296
318
|
get_headers = get_request_data.headers
|
297
319
|
response: Response = run_keyword(
|
@@ -300,3 +322,10 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
|
|
300
322
|
if response.ok:
|
301
323
|
original_data = response.json()
|
302
324
|
return original_data
|
325
|
+
|
326
|
+
@staticmethod
|
327
|
+
def get_keyword_names() -> list[str]:
|
328
|
+
"""Curated keywords for libdoc and libspec."""
|
329
|
+
if getenv("HIDE_INHERITED_KEYWORDS") == "true":
|
330
|
+
return KEYWORD_NAMES
|
331
|
+
return KEYWORD_NAMES + LIBCORE_KEYWORD_NAMES
|
OpenApiDriver/openapi_reader.py
CHANGED
@@ -1,116 +1,114 @@
|
|
1
|
-
"""Module holding the OpenApiReader reader_class implementation."""
|
2
|
-
|
3
|
-
from typing import
|
4
|
-
|
5
|
-
from DataDriver.AbstractReaderClass import AbstractReaderClass
|
6
|
-
from DataDriver.ReaderConfig import TestCaseData
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
self.
|
18
|
-
self.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
and self.
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
response
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
"${
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
if
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
def _get_tag_list(tags: List[str], method: str, response: str) -> List[str]:
|
116
|
-
return [*tags, f"Method: {method.upper()}", f"Response: {response}"]
|
1
|
+
"""Module holding the OpenApiReader reader_class implementation."""
|
2
|
+
|
3
|
+
from typing import Sequence
|
4
|
+
|
5
|
+
from DataDriver.AbstractReaderClass import AbstractReaderClass
|
6
|
+
from DataDriver.ReaderConfig import TestCaseData
|
7
|
+
|
8
|
+
from OpenApiLibCore.models import PathItemObject
|
9
|
+
|
10
|
+
|
11
|
+
class Test:
|
12
|
+
"""
|
13
|
+
Helper class to support ignoring endpoint responses when generating the test cases.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self, path: str, method: str, response: str | int) -> None:
|
17
|
+
self.path = path
|
18
|
+
self.method = method.lower()
|
19
|
+
self.response = str(response)
|
20
|
+
|
21
|
+
def __eq__(self, other: object) -> bool:
|
22
|
+
if not isinstance(other, type(self)):
|
23
|
+
return False
|
24
|
+
return (
|
25
|
+
self.path == other.path
|
26
|
+
and self.method == other.method
|
27
|
+
and self.response == other.response
|
28
|
+
)
|
29
|
+
|
30
|
+
|
31
|
+
class OpenApiReader(AbstractReaderClass):
|
32
|
+
"""Implementation of the reader_class used by DataDriver."""
|
33
|
+
|
34
|
+
def get_data_from_source(self) -> list[TestCaseData]:
|
35
|
+
test_data: list[TestCaseData] = []
|
36
|
+
|
37
|
+
read_paths_method = getattr(self, "read_paths_method")
|
38
|
+
paths: dict[str, PathItemObject] = read_paths_method()
|
39
|
+
self._filter_paths(paths)
|
40
|
+
|
41
|
+
ignored_responses_ = [
|
42
|
+
str(response) for response in getattr(self, "ignored_responses", [])
|
43
|
+
]
|
44
|
+
|
45
|
+
ignored_tests = [Test(*test) for test in getattr(self, "ignored_testcases", [])]
|
46
|
+
|
47
|
+
for path, path_item in paths.items():
|
48
|
+
path_operations = path_item.get_operations()
|
49
|
+
|
50
|
+
# by reseversing the items, post/put operations come before get and delete
|
51
|
+
for method, operation_data in reversed(path_operations.items()):
|
52
|
+
tags_from_spec = operation_data.tags
|
53
|
+
for response in operation_data.responses.keys():
|
54
|
+
# 'default' applies to all status codes that are not specified, in
|
55
|
+
# which case we don't know what to expect and therefore can't verify
|
56
|
+
if (
|
57
|
+
response == "default"
|
58
|
+
or response in ignored_responses_
|
59
|
+
or Test(path, method, response) in ignored_tests
|
60
|
+
):
|
61
|
+
continue
|
62
|
+
|
63
|
+
tag_list = _get_tag_list(
|
64
|
+
tags=tags_from_spec, method=method, response=response
|
65
|
+
)
|
66
|
+
test_data.append(
|
67
|
+
TestCaseData(
|
68
|
+
arguments={
|
69
|
+
"${path}": path,
|
70
|
+
"${method}": method.upper(),
|
71
|
+
"${status_code}": response,
|
72
|
+
},
|
73
|
+
tags=tag_list,
|
74
|
+
),
|
75
|
+
)
|
76
|
+
return test_data
|
77
|
+
|
78
|
+
def _filter_paths(self, paths: dict[str, PathItemObject]) -> None:
|
79
|
+
def matches_include_pattern(path: str) -> bool:
|
80
|
+
for included_path in included_paths:
|
81
|
+
if path == included_path:
|
82
|
+
return True
|
83
|
+
if included_path.endswith("*"):
|
84
|
+
wildcard_include, _, _ = included_path.partition("*")
|
85
|
+
if path.startswith(wildcard_include):
|
86
|
+
return True
|
87
|
+
return False
|
88
|
+
|
89
|
+
def matches_ignore_pattern(path: str) -> bool:
|
90
|
+
for ignored_path in ignored_paths:
|
91
|
+
if path == ignored_path:
|
92
|
+
return True
|
93
|
+
|
94
|
+
if ignored_path.endswith("*"):
|
95
|
+
wildcard_ignore, _, _ = ignored_path.partition("*")
|
96
|
+
if path.startswith(wildcard_ignore):
|
97
|
+
return True
|
98
|
+
return False
|
99
|
+
|
100
|
+
if included_paths := getattr(self, "included_paths", ()):
|
101
|
+
path_list = list(paths.keys())
|
102
|
+
for path in path_list:
|
103
|
+
if not matches_include_pattern(path):
|
104
|
+
paths.pop(path)
|
105
|
+
|
106
|
+
if ignored_paths := getattr(self, "ignored_paths", ()):
|
107
|
+
path_list = list(paths.keys())
|
108
|
+
for path in path_list:
|
109
|
+
if matches_ignore_pattern(path):
|
110
|
+
paths.pop(path)
|
111
|
+
|
112
|
+
|
113
|
+
def _get_tag_list(tags: Sequence[str], method: str, response: str) -> list[str]:
|
114
|
+
return [*tags, f"Method: {method.upper()}", f"Response: {response}"]
|