robotframework-openapitools 0.3.0__tar.gz → 0.4.0__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.
Files changed (41) hide show
  1. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/PKG-INFO +8 -7
  2. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/pyproject.toml +9 -9
  3. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/openapi_executors.py +9 -4
  4. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/openapidriver.libspec +4 -4
  5. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/dto_base.py +15 -6
  6. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/openapi_libcore.libspec +43 -27
  7. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/openapi_libcore.py +56 -3
  8. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/LICENSE +0 -0
  9. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/docs/README.md +0 -0
  10. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/__init__.py +0 -0
  11. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/openapi_reader.py +0 -0
  12. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/openapidriver.py +0 -0
  13. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/py.typed +0 -0
  14. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/__init__.py +0 -0
  15. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/dto_utils.py +0 -0
  16. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/oas_cache.py +0 -0
  17. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/py.typed +0 -0
  18. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/value_utils.py +0 -0
  19. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/__init__.py +0 -0
  20. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/__main__.py +0 -0
  21. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/auth.py +0 -0
  22. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/cli.py +0 -0
  23. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/core.py +0 -0
  24. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/__init__.py +0 -0
  25. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/generate.py +0 -0
  26. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/__init__.py +0 -0
  27. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/api.py +0 -0
  28. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/definition.py +0 -0
  29. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/endpoint.py +0 -0
  30. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/parameter.py +0 -0
  31. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/response.py +0 -0
  32. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/tag.py +0 -0
  33. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/utils.py +0 -0
  34. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/templates/api_init.jinja +0 -0
  35. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/templates/models.jinja +0 -0
  36. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/generate/templates/paths.jinja +0 -0
  37. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/logger.py +0 -0
  38. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/validate/__init__.py +0 -0
  39. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/validate/core.py +0 -0
  40. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/validate/schema.py +0 -0
  41. {robotframework_openapitools-0.3.0 → robotframework_openapitools-0.4.0}/src/roboswag/validate/text_response.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-openapitools
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A set of Robot Framework libraries to test APIs for which the OAS is available.
5
5
  Home-page: https://github.com/MarketSquare/robotframework-openapitools
6
6
  License: Apache-2.0
@@ -18,18 +18,19 @@ Classifier: Programming Language :: Python :: 3.9
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
21
22
  Classifier: Topic :: Software Development :: Testing
22
23
  Classifier: Topic :: Software Development :: Testing :: Acceptance
23
24
  Requires-Dist: Faker (>=23.1.0)
24
- Requires-Dist: Jinja2 (>=3.1.2,<4.0.0)
25
+ Requires-Dist: Jinja2 (>=3.1.2)
25
26
  Requires-Dist: black (>=24.1.0)
26
- Requires-Dist: openapi-core (>=0.19.0,<0.20.0)
27
- Requires-Dist: prance[cli] (>=23,<24)
28
- Requires-Dist: requests (>=2.31.0,<3.0.0)
29
- Requires-Dist: rich_click (>=1.7.0,<2.0.0)
27
+ Requires-Dist: openapi-core (>=0.19.0)
28
+ Requires-Dist: prance[cli] (>=23)
29
+ Requires-Dist: requests (>=2.31.0)
30
+ Requires-Dist: rich_click (>=1.7.0)
30
31
  Requires-Dist: robotframework (>=6.0.0,!=7.0.0)
31
32
  Requires-Dist: robotframework-datadriver (>=1.10.0)
32
- Requires-Dist: rstr (>=3.2.0,<4.0.0)
33
+ Requires-Dist: rstr (>=3.2.0)
33
34
  Description-Content-Type: text/markdown
34
35
 
35
36
  # OpenApiTools for Robot Framework
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name="robotframework-openapitools"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "A set of Robot Framework libraries to test APIs for which the OAS is available."
5
5
  license = "Apache-2.0"
6
6
  authors = [
@@ -29,16 +29,16 @@ include = ["*.libspec"]
29
29
 
30
30
  [tool.poetry.dependencies]
31
31
  python = "^3.8"
32
- robotframework = ">=6.0.0, !=7.0.0"
32
+ robotframework = {version = ">=6.0.0, !=7.0.0", allow-prereleases = false }
33
33
  robotframework-datadriver = ">=1.10.0"
34
- requests = "^2.31.0"
35
- prance = {version = "^23", extras = ["CLI"]}
34
+ requests = ">=2.31.0"
35
+ prance = {version = ">=23", extras = ["CLI"]}
36
36
  Faker = ">=23.1.0"
37
- rstr = "^3.2.0"
38
- openapi-core = "^0.19.0"
39
- rich_click = "^1.7.0"
37
+ rstr = ">=3.2.0"
38
+ openapi-core = ">=0.19.0"
39
+ rich_click = ">=1.7.0"
40
40
  black = ">=24.1.0"
41
- Jinja2 = "^3.1.2"
41
+ Jinja2 = ">=3.1.2"
42
42
 
43
43
  [tool.poetry.group.dev.dependencies]
44
44
  invoke = ">=2.2.0"
@@ -69,7 +69,7 @@ build-backend = "poetry.core.masonry.api"
69
69
  [tool.coverage.run]
70
70
  branch = true
71
71
  parallel = true
72
- source = ["src"]
72
+ source = ["src/OpenApiDriver", "src/OpenApiLibCore"]
73
73
 
74
74
  [tool.coverage.report]
75
75
  exclude_lines = [
@@ -118,8 +118,9 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
118
118
  Perform a request for the provided 'path' and 'method' where the url for
119
119
  the `path` is invalidated.
120
120
 
121
- This keyword will be `SKIPPED` if the path contains no parts that
122
- can be invalidated.
121
+ This keyword will be `SKIPPED` if the path contains no parts
122
+ that can be invalidated and there is no mapping for a
123
+ PathPropertiesConstraint for the `expected_status_code`.
123
124
 
124
125
  The optional `expected_status_code` parameter (default: 404) can be set to the
125
126
  expected status code for APIs that do not return a 404 on invalid urls.
@@ -131,7 +132,11 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
131
132
  """
132
133
  valid_url: str = run_keyword("get_valid_url", path, method)
133
134
 
134
- if not (url := run_keyword("get_invalidated_url", valid_url)):
135
+ if not (
136
+ url := run_keyword(
137
+ "get_invalidated_url", valid_url, path, method, expected_status_code
138
+ )
139
+ ):
135
140
  raise SkipExecution(
136
141
  f"Path {path} does not contain resource references that "
137
142
  f"can be invalidated."
@@ -194,7 +199,7 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
194
199
  }
195
200
  invalidation_keywords = []
196
201
 
197
- if request_data.dto.get_relations_for_error_code(status_code):
202
+ if request_data.dto.get_body_relations_for_error_code(status_code):
198
203
  invalidation_keywords.append("get_invalid_json_data")
199
204
  if request_data.dto.get_parameter_relations_for_error_code(status_code):
200
205
  invalidation_keywords.append("get_invalidated_parameters")
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <keywordspec name="OpenApiDriver" type="LIBRARY" format="HTML" scope="SUITE" generated="2024-08-09T12:54:30+00:00" specversion="6" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapidriver.py" lineno="352">
3
- <version>0.3.0</version>
2
+ <keywordspec name="OpenApiDriver" type="LIBRARY" format="HTML" scope="SUITE" generated="2024-12-04T12:16:37+00:00" specversion="6" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapidriver.py" lineno="352">
3
+ <version>0.4.0</version>
4
4
  <doc>&lt;p&gt;Visit the &lt;a href="https://github.com/MarketSquare/robotframework-openapidriver"&gt;library page&lt;/a&gt; for an introduction and examples.&lt;/p&gt;</doc>
5
5
  <tags>
6
6
  </tags>
@@ -265,7 +265,7 @@
265
265
  </init>
266
266
  </inits>
267
267
  <keywords>
268
- <kw name="Test Endpoint" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapi_executors.py" lineno="156">
268
+ <kw name="Test Endpoint" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapi_executors.py" lineno="161">
269
269
  <arguments repr="path: str, method: str, status_code: int">
270
270
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
271
271
  <name>path</name>
@@ -318,7 +318,7 @@
318
318
  </arg>
319
319
  </arguments>
320
320
  <doc>&lt;p&gt;Perform a request for the provided 'path' and 'method' where the url for the &lt;a href="#type-Path" class="name"&gt;path&lt;/a&gt; is invalidated.&lt;/p&gt;
321
- &lt;p&gt;This keyword will be &lt;span class="name"&gt;SKIPPED&lt;/span&gt; if the path contains no parts that can be invalidated.&lt;/p&gt;
321
+ &lt;p&gt;This keyword will be &lt;span class="name"&gt;SKIPPED&lt;/span&gt; if the path contains no parts that can be invalidated and there is no mapping for a PathPropertiesConstraint for the &lt;span class="name"&gt;expected_status_code&lt;/span&gt;.&lt;/p&gt;
322
322
  &lt;p&gt;The optional &lt;span class="name"&gt;expected_status_code&lt;/span&gt; parameter (default: 404) can be set to the expected status code for APIs that do not return a 404 on invalid urls.&lt;/p&gt;
323
323
  &lt;p&gt;&amp;gt; Note: Depending on API design, the url may be validated before or after validation of headers, query parameters and / or (json) body. By default, no parameters are send with the request. The &lt;span class="name"&gt;require_body_for_invalid_url&lt;/span&gt; parameter can be set to &lt;span class="name"&gt;True&lt;/span&gt; if needed.&lt;/p&gt;</doc>
324
324
  <shortdoc>Perform a request for the provided 'path' and 'method' where the url for the `path` is invalidated.</shortdoc>
@@ -99,6 +99,8 @@ class PathPropertiesConstraint(ResourceRelation):
99
99
 
100
100
  path: str
101
101
  property_name: str = "id"
102
+ invalid_value: Any = NOT_SET
103
+ invalid_value_error_code: int = 422
102
104
  error_code: int = 404
103
105
 
104
106
 
@@ -111,6 +113,7 @@ class PropertyValueConstraint(ResourceRelation):
111
113
  invalid_value: Any = NOT_SET
112
114
  invalid_value_error_code: int = 422
113
115
  error_code: int = 422
116
+ treat_as_mandatory: bool = False
114
117
 
115
118
 
116
119
  @dataclass
@@ -190,6 +193,14 @@ class Dto(ABC):
190
193
  ]
191
194
  return relations
192
195
 
196
+ def get_body_relations_for_error_code(self, error_code: int) -> List[Relation]:
197
+ """
198
+ Return the list of Relations associated with the given error_code that are
199
+ applicable to the body / payload of the request.
200
+ """
201
+ all_relations = self.get_relations_for_error_code(error_code=error_code)
202
+ return [r for r in all_relations if not isinstance(r, PathPropertiesConstraint)]
203
+
193
204
  def get_invalidated_data(
194
205
  self,
195
206
  schema: Dict[str, Any],
@@ -207,18 +218,16 @@ class Dto(ABC):
207
218
  r for r in relations if not isinstance(r, PathPropertiesConstraint)
208
219
  ]
209
220
  property_names = [r.property_name for r in relations]
210
- if status_code == invalid_property_default_code:
221
+ if status_code == invalid_property_default_code and schema.get("properties"):
211
222
  # add all properties defined in the schema, including optional properties
212
223
  property_names.extend((schema["properties"].keys()))
213
- # remove duplicates
214
- property_names = list(set(property_names))
215
224
  if not property_names:
216
225
  raise ValueError(
217
226
  f"No property can be invalidated to cause status_code {status_code}"
218
227
  )
219
- # shuffle the property_names so different properties on the Dto are invalidated
220
- # when rerunning the test
221
- shuffle(property_names)
228
+ # Remove duplicates, then shuffle the property_names so different properties on
229
+ # the Dto are invalidated when rerunning the test.
230
+ shuffle(list(set(property_names)))
222
231
  for property_name in property_names:
223
232
  # if possible, invalidate a constraint but send otherwise valid data
224
233
  id_dependencies = [
@@ -1,12 +1,12 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <keywordspec name="OpenApiLibCore" type="LIBRARY" format="HTML" scope="SUITE" generated="2024-08-09T12:54:30+00:00" specversion="6" source="/workspaces/robotframework-openapitools/src/OpenApiLibCore/openapi_libcore.py" lineno="431">
3
- <version>0.3.0</version>
2
+ <keywordspec name="OpenApiLibCore" type="LIBRARY" format="HTML" scope="SUITE" generated="2024-12-04T12:16:36+00:00" specversion="6" source="/workspaces/robotframework-openapitools/src/OpenApiLibCore/openapi_libcore.py" lineno="457">
3
+ <version>0.4.0</version>
4
4
  <doc>&lt;p&gt;Main class providing the keywords and core logic to interact with an OpenAPI server.&lt;/p&gt;
5
5
  &lt;p&gt;Visit the &lt;a href="https://github.com/MarketSquare/robotframework-openapi-libcore"&gt;library page&lt;/a&gt; for an introduction.&lt;/p&gt;</doc>
6
6
  <tags>
7
7
  </tags>
8
8
  <inits>
9
- <init name="__init__" lineno="439">
9
+ <init name="__init__" lineno="465">
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] | None = None, require_body_for_invalid_url: bool = False, recursion_limit: int = 1, recursion_default: Any = {}, username: str = , password: str = , security_token: str = , auth: AuthBase | None = None, cert: str | Tuple[str, str] | None = None, verify_tls: bool | str | None = True, extra_headers: Dict[str, str] | None = None, cookies: Dict[str, str] | RequestsCookieJar | None = None, proxies: Dict[str, str] | None = None">
11
11
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="source: str">
12
12
  <name>source</name>
@@ -214,7 +214,7 @@
214
214
  </init>
215
215
  </inits>
216
216
  <keywords>
217
- <kw name="Authorized Request" lineno="1683">
217
+ <kw name="Authorized Request" lineno="1731">
218
218
  <arguments repr="url: str, method: str, params: Dict[str, Any] | None = None, headers: Dict[str, str] | None = None, json_data: Dict[str, Dict[str, JSON] | List[JSON] | str | int | float | bool | None] | List[Dict[str, JSON] | List[JSON] | str | int | float | bool | None] | str | int | float | bool | None = None, data: Any = None, files: Any = None">
219
219
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
220
220
  <name>url</name>
@@ -307,7 +307,7 @@
307
307
  &lt;p&gt;&amp;gt; Note: provided username / password or auth objects take precedence over token based security&lt;/p&gt;</doc>
308
308
  <shortdoc>Perform a request using the security token or authentication set in the library.</shortdoc>
309
309
  </kw>
310
- <kw name="Ensure In Use" lineno="1587">
310
+ <kw name="Ensure In Use" lineno="1635">
311
311
  <arguments repr="url: str, resource_relation: IdReference">
312
312
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
313
313
  <name>url</name>
@@ -321,7 +321,7 @@
321
321
  <doc>&lt;p&gt;Ensure that the (right-most) &lt;span class="name"&gt;id&lt;/span&gt; of the resource referenced by the &lt;span class="name"&gt;url&lt;/span&gt; is used by the resource defined by the &lt;span class="name"&gt;resource_relation&lt;/span&gt;.&lt;/p&gt;</doc>
322
322
  <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>
323
323
  </kw>
324
- <kw name="Get Ids From Url" lineno="958">
324
+ <kw name="Get Ids From Url" lineno="984">
325
325
  <arguments repr="url: str">
326
326
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
327
327
  <name>url</name>
@@ -334,7 +334,7 @@
334
334
  <doc>&lt;p&gt;Perform a GET request on the &lt;span class="name"&gt;url&lt;/span&gt; and return the list of resource &lt;span class="name"&gt;ids&lt;/span&gt; from the response.&lt;/p&gt;</doc>
335
335
  <shortdoc>Perform a GET request on the `url` and return the list of resource `ids` from the response.</shortdoc>
336
336
  </kw>
337
- <kw name="Get Invalid Json Data" lineno="1364">
337
+ <kw name="Get Invalid Json Data" lineno="1409">
338
338
  <arguments repr="url: str, method: str, status_code: int, request_data: RequestData">
339
339
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
340
340
  <name>url</name>
@@ -361,7 +361,7 @@
361
361
  &lt;p&gt;&amp;gt; Note: applicable UniquePropertyValueConstraint and IdReference Relations are considered before changes to &lt;span class="name"&gt;json_data&lt;/span&gt; are made.&lt;/p&gt;</doc>
362
362
  <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>
363
363
  </kw>
364
- <kw name="Get Invalidated Parameters" lineno="1412">
364
+ <kw name="Get Invalidated Parameters" lineno="1460">
365
365
  <arguments repr="status_code: int, request_data: RequestData">
366
366
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="status_code: int">
367
367
  <name>status_code</name>
@@ -385,22 +385,37 @@
385
385
  <doc>&lt;p&gt;Returns a version of &lt;span class="name"&gt;params, headers&lt;/span&gt; as present on &lt;span class="name"&gt;request_data&lt;/span&gt; that has been modified to cause the provided &lt;span class="name"&gt;status_code&lt;/span&gt;.&lt;/p&gt;</doc>
386
386
  <shortdoc>Returns a version of `params, headers` as present on `request_data` that has been modified to cause the provided `status_code`.</shortdoc>
387
387
  </kw>
388
- <kw name="Get Invalidated Url" lineno="1330">
389
- <arguments repr="valid_url: str">
388
+ <kw name="Get Invalidated Url" lineno="1356">
389
+ <arguments repr="valid_url: str, path: str = , method: str = , expected_status_code: int = 404">
390
390
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="valid_url: str">
391
391
  <name>valid_url</name>
392
392
  <type name="str" typedoc="string"/>
393
393
  </arg>
394
+ <arg kind="POSITIONAL_OR_NAMED" required="false" repr="path: str = ">
395
+ <name>path</name>
396
+ <type name="str" typedoc="string"/>
397
+ <default/>
398
+ </arg>
399
+ <arg kind="POSITIONAL_OR_NAMED" required="false" repr="method: str = ">
400
+ <name>method</name>
401
+ <type name="str" typedoc="string"/>
402
+ <default/>
403
+ </arg>
404
+ <arg kind="POSITIONAL_OR_NAMED" required="false" repr="expected_status_code: int = 404">
405
+ <name>expected_status_code</name>
406
+ <type name="int" typedoc="integer"/>
407
+ <default>404</default>
408
+ </arg>
394
409
  </arguments>
395
410
  <returntype name="Union" union="true">
396
411
  <type name="str" typedoc="string"/>
397
412
  <type name="None" typedoc="None"/>
398
413
  </returntype>
399
- <doc>&lt;p&gt;Return an url with all the path parameters in the &lt;span class="name"&gt;valid_url&lt;/span&gt; replaced by a random UUID.&lt;/p&gt;
414
+ <doc>&lt;p&gt;Return an url with all the path parameters in the &lt;span class="name"&gt;valid_url&lt;/span&gt; replaced by a random UUID if no PathPropertiesConstraint is mapped for the &lt;a href="#type-Path" class="name"&gt;path&lt;/a&gt;, &lt;span class="name"&gt;method&lt;/span&gt; and &lt;span class="name"&gt;expected_status_code&lt;/span&gt;. If a PathPropertiesConstraint is mapped, the &lt;span class="name"&gt;invalid_value&lt;/span&gt; is returned.&lt;/p&gt;
400
415
  &lt;p&gt;Raises ValueError if the valid_url cannot be invalidated.&lt;/p&gt;</doc>
401
- <shortdoc>Return an url with all the path parameters in the `valid_url` replaced by a random UUID.</shortdoc>
416
+ <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 `path`, `method` and `expected_status_code`. If a PathPropertiesConstraint is mapped, the `invalid_value` is returned.</shortdoc>
402
417
  </kw>
403
- <kw name="Get Json Data For Dto Class" lineno="1206">
418
+ <kw name="Get Json Data For Dto Class" lineno="1232">
404
419
  <arguments repr="schema: Dict[str, Any], dto_class: Dto | Type[Dto], operation_id: str = ">
405
420
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="schema: Dict[str, Any]">
406
421
  <name>schema</name>
@@ -434,7 +449,7 @@
434
449
  <doc>&lt;p&gt;Generate a valid (json-compatible) dict for all the &lt;span class="name"&gt;dto_class&lt;/span&gt; properties.&lt;/p&gt;</doc>
435
450
  <shortdoc>Generate a valid (json-compatible) dict for all the `dto_class` properties.</shortdoc>
436
451
  </kw>
437
- <kw name="Get Json Data With Conflict" lineno="1631">
452
+ <kw name="Get Json Data With Conflict" lineno="1679">
438
453
  <arguments repr="url: str, method: str, dto: Dto, conflict_status_code: int">
439
454
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
440
455
  <name>url</name>
@@ -460,7 +475,7 @@
460
475
  <doc>&lt;p&gt;Return &lt;span class="name"&gt;json_data&lt;/span&gt; based on the &lt;span class="name"&gt;UniquePropertyValueConstraint&lt;/span&gt; that must be returned by the &lt;span class="name"&gt;get_relations&lt;/span&gt; implementation on the &lt;span class="name"&gt;dto&lt;/span&gt; for the given &lt;span class="name"&gt;conflict_status_code&lt;/span&gt;.&lt;/p&gt;</doc>
461
476
  <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>
462
477
  </kw>
463
- <kw name="Get Parameterized Endpoint From Url" lineno="1352">
478
+ <kw name="Get Parameterized Endpoint From Url" lineno="1397">
464
479
  <arguments repr="url: str">
465
480
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
466
481
  <name>url</name>
@@ -471,7 +486,7 @@
471
486
  <doc>&lt;p&gt;Return the endpoint as found in the &lt;span class="name"&gt;paths&lt;/span&gt; section based on the given &lt;span class="name"&gt;url&lt;/span&gt;.&lt;/p&gt;</doc>
472
487
  <shortdoc>Return the endpoint as found in the `paths` section based on the given `url`.</shortdoc>
473
488
  </kw>
474
- <kw name="Get Request Data" lineno="998">
489
+ <kw name="Get Request Data" lineno="1024">
475
490
  <arguments repr="endpoint: str, method: str">
476
491
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="endpoint: str">
477
492
  <name>endpoint</name>
@@ -486,7 +501,7 @@
486
501
  <doc>&lt;p&gt;Return an object with valid request data for body, headers and query params.&lt;/p&gt;</doc>
487
502
  <shortdoc>Return an object with valid request data for body, headers and query params.</shortdoc>
488
503
  </kw>
489
- <kw name="Get Valid Id For Endpoint" lineno="862">
504
+ <kw name="Get Valid Id For Endpoint" lineno="888">
490
505
  <arguments repr="endpoint: str, method: str">
491
506
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="endpoint: str">
492
507
  <name>endpoint</name>
@@ -506,7 +521,7 @@
506
521
  &lt;p&gt;To prevent resource conflicts with other test cases, a new resource is created (POST) if possible.&lt;/p&gt;</doc>
507
522
  <shortdoc>Support keyword that returns the `id` for an existing resource at `endpoint`.</shortdoc>
508
523
  </kw>
509
- <kw name="Get Valid Url" lineno="822">
524
+ <kw name="Get Valid Url" lineno="848">
510
525
  <arguments repr="endpoint: str, method: str">
511
526
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="endpoint: str">
512
527
  <name>endpoint</name>
@@ -523,7 +538,7 @@
523
538
  &lt;p&gt;&amp;gt; Note: if valid ids cannot be retrieved within the scope of the API, the &lt;span class="name"&gt;PathPropertiesConstraint&lt;/span&gt; Relation can be used. More information can be found &lt;a href="https://marketsquare.github.io/robotframework-openapi-libcore/advanced_use.html"&gt;here&lt;/a&gt;.&lt;/p&gt;</doc>
524
539
  <shortdoc>This keyword returns a valid url for the given `endpoint` and `method`.</shortdoc>
525
540
  </kw>
526
- <kw name="Perform Validated Request" lineno="1730">
541
+ <kw name="Perform Validated Request" lineno="1778">
527
542
  <arguments repr="path: str, status_code: int, request_values: RequestValues, original_data: Dict[str, Any] | None = None">
528
543
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
529
544
  <name>path</name>
@@ -552,7 +567,7 @@
552
567
  <doc>&lt;p&gt;This keyword first calls the Authorized Request keyword, then the Validate Response keyword and finally validates, for &lt;span class="name"&gt;DELETE&lt;/span&gt; operations, whether the target resource was indeed deleted (OK response) or not (error responses).&lt;/p&gt;</doc>
553
568
  <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>
554
569
  </kw>
555
- <kw name="Set Auth" lineno="677">
570
+ <kw name="Set Auth" lineno="703">
556
571
  <arguments repr="auth: AuthBase">
557
572
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="auth: AuthBase">
558
573
  <name>auth</name>
@@ -563,7 +578,7 @@
563
578
  &lt;p&gt;After calling this keyword, subsequent requests will use the provided &lt;span class="name"&gt;auth&lt;/span&gt; instance.&lt;/p&gt;</doc>
564
579
  <shortdoc>Set the `auth` used for authentication after the library is imported.</shortdoc>
565
580
  </kw>
566
- <kw name="Set Basic Auth" lineno="665">
581
+ <kw name="Set Basic Auth" lineno="691">
567
582
  <arguments repr="username: str, password: str">
568
583
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="username: str">
569
584
  <name>username</name>
@@ -578,7 +593,7 @@
578
593
  &lt;p&gt;After calling this keyword, subsequent requests will use the provided credentials.&lt;/p&gt;</doc>
579
594
  <shortdoc>Set the `username` and `password` used for basic authentication after the library is imported.</shortdoc>
580
595
  </kw>
581
- <kw name="Set Extra Headers" lineno="687">
596
+ <kw name="Set Extra Headers" lineno="713">
582
597
  <arguments repr="extra_headers: Dict[str, str]">
583
598
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="extra_headers: Dict[str, str]">
584
599
  <name>extra_headers</name>
@@ -592,7 +607,7 @@
592
607
  &lt;p&gt;After calling this keyword, subsequent requests will use the provided &lt;span class="name"&gt;extra_headers&lt;/span&gt;.&lt;/p&gt;</doc>
593
608
  <shortdoc>Set the `extra_headers` used in requests after the library is imported.</shortdoc>
594
609
  </kw>
595
- <kw name="Set Origin" lineno="642">
610
+ <kw name="Set Origin" lineno="668">
596
611
  <arguments repr="origin: str">
597
612
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="origin: str">
598
613
  <name>origin</name>
@@ -604,7 +619,7 @@
604
619
  &lt;p&gt;In combination with OpenApiLibCore, the &lt;span class="name"&gt;origin&lt;/span&gt; can be used at any point to target another server that hosts an API that complies to the same OAS.&lt;/p&gt;</doc>
605
620
  <shortdoc>Set the `origin` after the library is imported.</shortdoc>
606
621
  </kw>
607
- <kw name="Set Security Token" lineno="656">
622
+ <kw name="Set Security Token" lineno="682">
608
623
  <arguments repr="security_token: str">
609
624
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="security_token: str">
610
625
  <name>security_token</name>
@@ -615,7 +630,7 @@
615
630
  &lt;p&gt;After calling this keyword, subsequent requests will use the provided token.&lt;/p&gt;</doc>
616
631
  <shortdoc>Set the `security_token` after the library is imported.</shortdoc>
617
632
  </kw>
618
- <kw name="Validate Resource Properties" lineno="1966">
633
+ <kw name="Validate Resource Properties" lineno="2019">
619
634
  <arguments repr="resource: Dict[str, Any], schema: Dict[str, Any]">
620
635
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="resource: Dict[str, Any]">
621
636
  <name>resource</name>
@@ -635,7 +650,7 @@
635
650
  <doc>&lt;p&gt;Validate that the &lt;span class="name"&gt;resource&lt;/span&gt; does not contain any properties that are not defined in the &lt;span class="name"&gt;schema_properties&lt;/span&gt;.&lt;/p&gt;</doc>
636
651
  <shortdoc>Validate that the `resource` does not contain any properties that are not defined in the `schema_properties`.</shortdoc>
637
652
  </kw>
638
- <kw name="Validate Response" lineno="1804">
653
+ <kw name="Validate Response" lineno="1852">
639
654
  <arguments repr="path: str, response: Response, original_data: Dict[str, Any] | None = None">
640
655
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
641
656
  <name>path</name>
@@ -668,7 +683,7 @@
668
683
  &lt;/ul&gt;</doc>
669
684
  <shortdoc>Validate the `response` by performing the following validations: - validate the `response` against the openapi schema for the `endpoint` - 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>
670
685
  </kw>
671
- <kw name="Validate Send Response" lineno="2084">
686
+ <kw name="Validate Send Response" lineno="2137">
672
687
  <arguments repr="response: Response, original_data: Dict[str, Any] | None = None">
673
688
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="response: Response">
674
689
  <name>response</name>
@@ -772,6 +787,7 @@
772
787
  <usage>Authorized Request</usage>
773
788
  <usage>Get Invalid Json Data</usage>
774
789
  <usage>Get Invalidated Parameters</usage>
790
+ <usage>Get Invalidated Url</usage>
775
791
  <usage>Get Json Data With Conflict</usage>
776
792
  <usage>Get Valid Id For Endpoint</usage>
777
793
  <usage>Perform Validated Request</usage>
@@ -378,7 +378,15 @@ class RequestData:
378
378
 
379
379
  def get_required_properties_dict(self) -> Dict[str, Any]:
380
380
  """Get the json-compatible dto data containing only the required properties."""
381
- required_properties = self.dto_schema.get("required", [])
381
+ relations = self.dto.get_relations()
382
+ mandatory_properties = [
383
+ relation.property_name
384
+ for relation in relations
385
+ if getattr(relation, "treat_as_mandatory", False)
386
+ ]
387
+ required_properties: List[str] = self.dto_schema.get("required", [])
388
+ required_properties.extend(mandatory_properties)
389
+
382
390
  required_properties_dict: Dict[str, Any] = {}
383
391
  for key, value in (self.dto.as_dict()).items():
384
392
  if key in required_properties:
@@ -414,16 +422,34 @@ class RequestData:
414
422
 
415
423
  def get_required_params(self) -> Dict[str, str]:
416
424
  """Get the params dict containing only the required query parameters."""
425
+ relations = self.dto.get_parameter_relations()
426
+ mandatory_properties = [
427
+ relation.property_name
428
+ for relation in relations
429
+ if getattr(relation, "treat_as_mandatory", False)
430
+ ]
431
+ mandatory_parameters = [p for p in mandatory_properties if p in self.parameters]
432
+
417
433
  required_parameters = [
418
434
  p.get("name") for p in self.parameters if p.get("required")
419
435
  ]
436
+ required_parameters.extend(mandatory_parameters)
420
437
  return {k: v for k, v in self.params.items() if k in required_parameters}
421
438
 
422
439
  def get_required_headers(self) -> Dict[str, str]:
423
440
  """Get the headers dict containing only the required headers."""
441
+ relations = self.dto.get_parameter_relations()
442
+ mandatory_properties = [
443
+ relation.property_name
444
+ for relation in relations
445
+ if getattr(relation, "treat_as_mandatory", False)
446
+ ]
447
+ mandatory_parameters = [p for p in mandatory_properties if p in self.parameters]
448
+
424
449
  required_parameters = [
425
450
  p.get("name") for p in self.parameters if p.get("required")
426
451
  ]
452
+ required_parameters.extend(mandatory_parameters)
427
453
  return {k: v for k, v in self.headers.items() if k in required_parameters}
428
454
 
429
455
 
@@ -1327,13 +1353,32 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1327
1353
  return json_data
1328
1354
 
1329
1355
  @keyword
1330
- def get_invalidated_url(self, valid_url: str) -> Optional[str]:
1356
+ def get_invalidated_url(
1357
+ self,
1358
+ valid_url: str,
1359
+ path: str = "",
1360
+ method: str = "",
1361
+ expected_status_code: int = 404,
1362
+ ) -> Optional[str]:
1331
1363
  """
1332
1364
  Return an url with all the path parameters in the `valid_url` replaced by a
1333
- random UUID.
1365
+ random UUID if no PathPropertiesConstraint is mapped for the `path`, `method`
1366
+ and `expected_status_code`.
1367
+ If a PathPropertiesConstraint is mapped, the `invalid_value` is returned.
1334
1368
 
1335
1369
  Raises ValueError if the valid_url cannot be invalidated.
1336
1370
  """
1371
+ dto_class = self.get_dto_class(endpoint=path, method=method)
1372
+ relations = dto_class.get_relations()
1373
+ paths = [
1374
+ p.invalid_value
1375
+ for p in relations
1376
+ if isinstance(p, PathPropertiesConstraint)
1377
+ and p.invalid_value_error_code == expected_status_code
1378
+ ]
1379
+ if paths:
1380
+ url = f"{self.base_url}{choice(paths)}"
1381
+ return url
1337
1382
  parameterized_endpoint = self.get_parameterized_endpoint_from_url(valid_url)
1338
1383
  parameterized_url = self.base_url + parameterized_endpoint
1339
1384
  valid_url_parts = list(reversed(valid_url.split("/")))
@@ -1377,6 +1422,9 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1377
1422
  """
1378
1423
  method = method.lower()
1379
1424
  data_relations = request_data.dto.get_relations_for_error_code(status_code)
1425
+ data_relations = [
1426
+ r for r in data_relations if not isinstance(r, PathPropertiesConstraint)
1427
+ ]
1380
1428
  if not data_relations:
1381
1429
  if not request_data.dto_schema:
1382
1430
  raise ValueError(
@@ -1873,6 +1921,11 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1873
1921
  response_spec["content"][content_type]["schema"]
1874
1922
  )
1875
1923
 
1924
+ response_types = response_schema.get("types")
1925
+ if response_types:
1926
+ # In case of oneOf / anyOf there can be multiple possible response types
1927
+ # which makes generic validation too complex
1928
+ return None
1876
1929
  response_type = response_schema.get("type", "undefined")
1877
1930
  if response_type not in ["object", "array"]:
1878
1931
  self._validate_value_type(value=json_response, expected_type=response_type)