robotframework-openapitools 0.2.3__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.2.3 → robotframework_openapitools-0.4.0}/PKG-INFO +8 -7
  2. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/pyproject.toml +9 -9
  3. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/openapi_executors.py +14 -9
  4. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/openapidriver.libspec +4 -4
  5. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/openapidriver.py +1 -1
  6. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/dto_base.py +15 -6
  7. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/openapi_libcore.libspec +152 -27
  8. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/openapi_libcore.py +156 -8
  9. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/LICENSE +0 -0
  10. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/docs/README.md +0 -0
  11. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/__init__.py +0 -0
  12. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/openapi_reader.py +0 -0
  13. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiDriver/py.typed +0 -0
  14. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/__init__.py +0 -0
  15. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/dto_utils.py +0 -0
  16. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/oas_cache.py +0 -0
  17. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/py.typed +0 -0
  18. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/OpenApiLibCore/value_utils.py +0 -0
  19. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/__init__.py +0 -0
  20. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/__main__.py +0 -0
  21. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/auth.py +0 -0
  22. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/cli.py +0 -0
  23. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/core.py +0 -0
  24. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/__init__.py +0 -0
  25. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/generate.py +0 -0
  26. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/__init__.py +0 -0
  27. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/api.py +0 -0
  28. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/definition.py +0 -0
  29. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/endpoint.py +0 -0
  30. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/parameter.py +0 -0
  31. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/response.py +0 -0
  32. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/tag.py +0 -0
  33. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/models/utils.py +0 -0
  34. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/templates/api_init.jinja +0 -0
  35. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/templates/models.jinja +0 -0
  36. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/generate/templates/paths.jinja +0 -0
  37. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/logger.py +0 -0
  38. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/validate/__init__.py +0 -0
  39. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/validate/core.py +0 -0
  40. {robotframework_openapitools-0.2.3 → robotframework_openapitools-0.4.0}/src/roboswag/validate/schema.py +0 -0
  41. {robotframework_openapitools-0.2.3 → 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.2.3
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.2.3"
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 = [
@@ -20,7 +20,7 @@ run_keyword = BuiltIn().run_keyword
20
20
  logger = getLogger(__name__)
21
21
 
22
22
 
23
- @library(scope="TEST SUITE", doc_format="ROBOT")
23
+ @library(scope="SUITE", doc_format="ROBOT")
24
24
  class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-attributes
25
25
  """Main class providing the keywords and core logic to perform endpoint validations."""
26
26
 
@@ -52,9 +52,13 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
52
52
  source=source,
53
53
  origin=origin,
54
54
  base_path=base_path,
55
+ response_validation=response_validation,
56
+ disable_server_validation=disable_server_validation,
55
57
  mappings_path=mappings_path,
56
58
  default_id_property_name=default_id_property_name,
59
+ invalid_property_default_response=invalid_property_default_response,
57
60
  faker_locale=faker_locale,
61
+ require_body_for_invalid_url=require_body_for_invalid_url,
58
62
  recursion_limit=recursion_limit,
59
63
  recursion_default=recursion_default,
60
64
  username=username,
@@ -67,10 +71,6 @@ class OpenApiExecutors(OpenApiLibCore): # pylint: disable=too-many-instance-att
67
71
  cookies=cookies,
68
72
  proxies=proxies,
69
73
  )
70
- self.response_validation = response_validation
71
- self.disable_server_validation = disable_server_validation
72
- self.require_body_for_invalid_url = require_body_for_invalid_url
73
- self.invalid_property_default_response = invalid_property_default_response
74
74
 
75
75
  @keyword
76
76
  def test_unauthorized(self, path: str, method: str) -> None:
@@ -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-06-20T15:30:31+00:00" specversion="6" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapidriver.py" lineno="352">
3
- <version>0.2.3</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>
@@ -134,7 +134,7 @@ from OpenApiDriver.openapi_executors import OpenApiExecutors, ValidationLevel
134
134
  from OpenApiDriver.openapi_reader import OpenApiReader
135
135
 
136
136
 
137
- @library(scope="TEST SUITE", doc_format="ROBOT")
137
+ @library(scope="SUITE", doc_format="ROBOT")
138
138
  class OpenApiDriver(OpenApiExecutors, DataDriver):
139
139
  """
140
140
  Visit the [https://github.com/MarketSquare/robotframework-openapidriver | library page]
@@ -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,13 +1,13 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <keywordspec name="OpenApiLibCore" type="LIBRARY" format="HTML" scope="SUITE" generated="2024-06-20T15:30:30+00:00" specversion="6" source="/workspaces/robotframework-openapitools/src/OpenApiLibCore/openapi_libcore.py" lineno="430">
3
- <version>0.2.3</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="438">
10
- <arguments repr="source: str, origin: str = , base_path: str = , mappings_path: str | Path = , invalid_property_default_response: int = 422, default_id_property_name: str = id, faker_locale: str | List[str] | None = None, 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">
9
+ <init name="__init__" lineno="465">
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>
13
13
  <type name="str" typedoc="string"/>
@@ -22,6 +22,16 @@
22
22
  <type name="str" typedoc="string"/>
23
23
  <default/>
24
24
  </arg>
25
+ <arg kind="POSITIONAL_OR_NAMED" required="false" repr="response_validation: ValidationLevel = WARN">
26
+ <name>response_validation</name>
27
+ <type name="ValidationLevel" typedoc="ValidationLevel"/>
28
+ <default>WARN</default>
29
+ </arg>
30
+ <arg kind="POSITIONAL_OR_NAMED" required="false" repr="disable_server_validation: bool = True">
31
+ <name>disable_server_validation</name>
32
+ <type name="bool" typedoc="boolean"/>
33
+ <default>True</default>
34
+ </arg>
25
35
  <arg kind="POSITIONAL_OR_NAMED" required="false" repr="mappings_path: str | Path = ">
26
36
  <name>mappings_path</name>
27
37
  <type name="Union" union="true">
@@ -51,6 +61,11 @@
51
61
  </type>
52
62
  <default>None</default>
53
63
  </arg>
64
+ <arg kind="POSITIONAL_OR_NAMED" required="false" repr="require_body_for_invalid_url: bool = False">
65
+ <name>require_body_for_invalid_url</name>
66
+ <type name="bool" typedoc="boolean"/>
67
+ <default>False</default>
68
+ </arg>
54
69
  <arg kind="POSITIONAL_OR_NAMED" required="false" repr="recursion_limit: int = 1">
55
70
  <name>recursion_limit</name>
56
71
  <type name="int" typedoc="integer"/>
@@ -147,6 +162,17 @@
147
162
  &lt;p&gt;The server (and port) of the target server. E.g. &lt;code&gt;https://localhost:8000&lt;/code&gt;&lt;/p&gt;
148
163
  &lt;h4&gt;base_path&lt;/h4&gt;
149
164
  &lt;p&gt;The routing between &lt;code&gt;origin&lt;/code&gt; and the endpoints as found in the &lt;code&gt;paths&lt;/code&gt; section in the openapi document. E.g. &lt;code&gt;/petshop/v2&lt;/code&gt;.&lt;/p&gt;
165
+ &lt;h3&gt;Test case execution&lt;/h3&gt;
166
+ &lt;h4&gt;response_validation&lt;/h4&gt;
167
+ &lt;p&gt;By default, a &lt;code&gt;WARN&lt;/code&gt; is logged when the Response received after a Request does not comply with the schema as defined in the openapi document for the given operation. The following values are supported:&lt;/p&gt;
168
+ &lt;ul&gt;
169
+ &lt;li&gt;&lt;code&gt;DISABLED&lt;/code&gt;: All Response validation errors will be ignored&lt;/li&gt;
170
+ &lt;li&gt;&lt;code&gt;INFO&lt;/code&gt;: Any Response validation erros will be logged at &lt;code&gt;INFO&lt;/code&gt; level&lt;/li&gt;
171
+ &lt;li&gt;&lt;code&gt;WARN&lt;/code&gt;: Any Response validation erros will be logged at &lt;code&gt;WARN&lt;/code&gt; level&lt;/li&gt;
172
+ &lt;li&gt;&lt;code&gt;STRICT&lt;/code&gt;: The Test Case will fail on any Response validation errors&lt;/li&gt;
173
+ &lt;/ul&gt;
174
+ &lt;h4&gt;disable_server_validation&lt;/h4&gt;
175
+ &lt;p&gt;If enabled by setting this parameter to &lt;code&gt;True&lt;/code&gt;, the Response validation will also include possible errors for Requests made to a server address that is not defined in the list of servers in the openapi document. This generally means that if there is a mismatch, every Test Case will raise this error. Note that &lt;code&gt;localhost&lt;/code&gt; and &lt;code&gt;127.0.0.1&lt;/code&gt; are not considered the same by Response validation.&lt;/p&gt;
150
176
  &lt;h3&gt;API-specific configurations&lt;/h3&gt;
151
177
  &lt;h4&gt;mappings_path&lt;/h4&gt;
152
178
  &lt;p&gt;See &lt;a href="https://marketsquare.github.io/robotframework-openapi-libcore/advanced_use.html"&gt;this page&lt;/a&gt; for an in-depth explanation.&lt;/p&gt;
@@ -157,6 +183,8 @@
157
183
  &lt;p&gt;If different property names are used for the unique identifier for different types of resources, an &lt;code&gt;ID_MAPPING&lt;/code&gt; can be implemented using the &lt;code&gt;mappings_path&lt;/code&gt;.&lt;/p&gt;
158
184
  &lt;h4&gt;faker_locale&lt;/h4&gt;
159
185
  &lt;p&gt;A locale string or list of locale strings to pass to the Faker library to be used in generation of string data for supported format types.&lt;/p&gt;
186
+ &lt;h4&gt;require_body_for_invalid_url&lt;/h4&gt;
187
+ &lt;p&gt;When a request is made against an invalid url, this usually is because of a "404" request; a request for a resource that does not exist. Depending on API implementation, when a request with a missing or invalid request body is made on a non-existent resource, either a 404 or a 422 or 400 Response is normally returned. If the API being tested processes the request body before checking if the requested resource exists, set this parameter to True.&lt;/p&gt;
160
188
  &lt;h3&gt;Parsing parameters&lt;/h3&gt;
161
189
  &lt;h4&gt;recursion_limit&lt;/h4&gt;
162
190
  &lt;p&gt;The recursion depth to which to fully parse recursive references before the &lt;span class="name"&gt;recursion_default&lt;/span&gt; is used to end the recursion.&lt;/p&gt;
@@ -186,8 +214,8 @@
186
214
  </init>
187
215
  </inits>
188
216
  <keywords>
189
- <kw name="Authorized Request" lineno="1607">
190
- <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">
217
+ <kw name="Authorized Request" lineno="1731">
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">
191
219
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
192
220
  <name>url</name>
193
221
  <type name="str" typedoc="string"/>
@@ -262,13 +290,24 @@
262
290
  </type>
263
291
  <default>None</default>
264
292
  </arg>
293
+ <arg kind="POSITIONAL_OR_NAMED" required="false" repr="data: Any = None">
294
+ <name>data</name>
295
+ <type name="Any" typedoc="Any"/>
296
+ <default>None</default>
297
+ </arg>
298
+ <arg kind="POSITIONAL_OR_NAMED" required="false" repr="files: Any = None">
299
+ <name>files</name>
300
+ <type name="Any" typedoc="Any"/>
301
+ <default>None</default>
302
+ </arg>
265
303
  </arguments>
266
304
  <returntype name="Response"/>
267
305
  <doc>&lt;p&gt;Perform a request using the security token or authentication set in the library.&lt;/p&gt;
306
+ &lt;p&gt;&lt;span class="name"&gt;json_data&lt;/span&gt;, &lt;span class="name"&gt;data&lt;/span&gt; and &lt;span class="name"&gt;files&lt;/span&gt; are passed to &lt;span class="name"&gt;requests.request&lt;/span&gt;s &lt;span class="name"&gt;json&lt;/span&gt;, &lt;span class="name"&gt;data&lt;/span&gt; and &lt;span class="name"&gt;files&lt;/span&gt; parameters unaltered. See the requests documentation for details: &lt;a href="https://requests.readthedocs.io/en/latest/api/#requests.request"&gt;https://requests.readthedocs.io/en/latest/api/#requests.request&lt;/a&gt;&lt;/p&gt;
268
307
  &lt;p&gt;&amp;gt; Note: provided username / password or auth objects take precedence over token based security&lt;/p&gt;</doc>
269
308
  <shortdoc>Perform a request using the security token or authentication set in the library.</shortdoc>
270
309
  </kw>
271
- <kw name="Ensure In Use" lineno="1511">
310
+ <kw name="Ensure In Use" lineno="1635">
272
311
  <arguments repr="url: str, resource_relation: IdReference">
273
312
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
274
313
  <name>url</name>
@@ -282,7 +321,7 @@
282
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>
283
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>
284
323
  </kw>
285
- <kw name="Get Ids From Url" lineno="882">
324
+ <kw name="Get Ids From Url" lineno="984">
286
325
  <arguments repr="url: str">
287
326
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
288
327
  <name>url</name>
@@ -295,7 +334,7 @@
295
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>
296
335
  <shortdoc>Perform a GET request on the `url` and return the list of resource `ids` from the response.</shortdoc>
297
336
  </kw>
298
- <kw name="Get Invalid Json Data" lineno="1288">
337
+ <kw name="Get Invalid Json Data" lineno="1409">
299
338
  <arguments repr="url: str, method: str, status_code: int, request_data: RequestData">
300
339
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
301
340
  <name>url</name>
@@ -322,7 +361,7 @@
322
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>
323
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>
324
363
  </kw>
325
- <kw name="Get Invalidated Parameters" lineno="1336">
364
+ <kw name="Get Invalidated Parameters" lineno="1460">
326
365
  <arguments repr="status_code: int, request_data: RequestData">
327
366
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="status_code: int">
328
367
  <name>status_code</name>
@@ -346,22 +385,37 @@
346
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>
347
386
  <shortdoc>Returns a version of `params, headers` as present on `request_data` that has been modified to cause the provided `status_code`.</shortdoc>
348
387
  </kw>
349
- <kw name="Get Invalidated Url" lineno="1254">
350
- <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">
351
390
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="valid_url: str">
352
391
  <name>valid_url</name>
353
392
  <type name="str" typedoc="string"/>
354
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>
355
409
  </arguments>
356
410
  <returntype name="Union" union="true">
357
411
  <type name="str" typedoc="string"/>
358
412
  <type name="None" typedoc="None"/>
359
413
  </returntype>
360
- <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;
361
415
  &lt;p&gt;Raises ValueError if the valid_url cannot be invalidated.&lt;/p&gt;</doc>
362
- <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>
363
417
  </kw>
364
- <kw name="Get Json Data For Dto Class" lineno="1130">
418
+ <kw name="Get Json Data For Dto Class" lineno="1232">
365
419
  <arguments repr="schema: Dict[str, Any], dto_class: Dto | Type[Dto], operation_id: str = ">
366
420
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="schema: Dict[str, Any]">
367
421
  <name>schema</name>
@@ -395,7 +449,7 @@
395
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>
396
450
  <shortdoc>Generate a valid (json-compatible) dict for all the `dto_class` properties.</shortdoc>
397
451
  </kw>
398
- <kw name="Get Json Data With Conflict" lineno="1555">
452
+ <kw name="Get Json Data With Conflict" lineno="1679">
399
453
  <arguments repr="url: str, method: str, dto: Dto, conflict_status_code: int">
400
454
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
401
455
  <name>url</name>
@@ -421,7 +475,7 @@
421
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>
422
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>
423
477
  </kw>
424
- <kw name="Get Parameterized Endpoint From Url" lineno="1276">
478
+ <kw name="Get Parameterized Endpoint From Url" lineno="1397">
425
479
  <arguments repr="url: str">
426
480
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="url: str">
427
481
  <name>url</name>
@@ -432,7 +486,7 @@
432
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>
433
487
  <shortdoc>Return the endpoint as found in the `paths` section based on the given `url`.</shortdoc>
434
488
  </kw>
435
- <kw name="Get Request Data" lineno="922">
489
+ <kw name="Get Request Data" lineno="1024">
436
490
  <arguments repr="endpoint: str, method: str">
437
491
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="endpoint: str">
438
492
  <name>endpoint</name>
@@ -447,7 +501,7 @@
447
501
  <doc>&lt;p&gt;Return an object with valid request data for body, headers and query params.&lt;/p&gt;</doc>
448
502
  <shortdoc>Return an object with valid request data for body, headers and query params.</shortdoc>
449
503
  </kw>
450
- <kw name="Get Valid Id For Endpoint" lineno="786">
504
+ <kw name="Get Valid Id For Endpoint" lineno="888">
451
505
  <arguments repr="endpoint: str, method: str">
452
506
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="endpoint: str">
453
507
  <name>endpoint</name>
@@ -467,7 +521,7 @@
467
521
  &lt;p&gt;To prevent resource conflicts with other test cases, a new resource is created (POST) if possible.&lt;/p&gt;</doc>
468
522
  <shortdoc>Support keyword that returns the `id` for an existing resource at `endpoint`.</shortdoc>
469
523
  </kw>
470
- <kw name="Get Valid Url" lineno="746">
524
+ <kw name="Get Valid Url" lineno="848">
471
525
  <arguments repr="endpoint: str, method: str">
472
526
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="endpoint: str">
473
527
  <name>endpoint</name>
@@ -484,7 +538,7 @@
484
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>
485
539
  <shortdoc>This keyword returns a valid url for the given `endpoint` and `method`.</shortdoc>
486
540
  </kw>
487
- <kw name="Perform Validated Request" lineno="1645">
541
+ <kw name="Perform Validated Request" lineno="1778">
488
542
  <arguments repr="path: str, status_code: int, request_values: RequestValues, original_data: Dict[str, Any] | None = None">
489
543
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
490
544
  <name>path</name>
@@ -513,19 +567,70 @@
513
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>
514
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>
515
569
  </kw>
516
- <kw name="Set Origin" lineno="607">
570
+ <kw name="Set Auth" lineno="703">
571
+ <arguments repr="auth: AuthBase">
572
+ <arg kind="POSITIONAL_OR_NAMED" required="true" repr="auth: AuthBase">
573
+ <name>auth</name>
574
+ <type name="AuthBase"/>
575
+ </arg>
576
+ </arguments>
577
+ <doc>&lt;p&gt;Set the &lt;span class="name"&gt;auth&lt;/span&gt; used for authentication after the library is imported.&lt;/p&gt;
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>
579
+ <shortdoc>Set the `auth` used for authentication after the library is imported.</shortdoc>
580
+ </kw>
581
+ <kw name="Set Basic Auth" lineno="691">
582
+ <arguments repr="username: str, password: str">
583
+ <arg kind="POSITIONAL_OR_NAMED" required="true" repr="username: str">
584
+ <name>username</name>
585
+ <type name="str" typedoc="string"/>
586
+ </arg>
587
+ <arg kind="POSITIONAL_OR_NAMED" required="true" repr="password: str">
588
+ <name>password</name>
589
+ <type name="str" typedoc="string"/>
590
+ </arg>
591
+ </arguments>
592
+ <doc>&lt;p&gt;Set the &lt;span class="name"&gt;username&lt;/span&gt; and &lt;span class="name"&gt;password&lt;/span&gt; used for basic authentication after the library is imported.&lt;/p&gt;
593
+ &lt;p&gt;After calling this keyword, subsequent requests will use the provided credentials.&lt;/p&gt;</doc>
594
+ <shortdoc>Set the `username` and `password` used for basic authentication after the library is imported.</shortdoc>
595
+ </kw>
596
+ <kw name="Set Extra Headers" lineno="713">
597
+ <arguments repr="extra_headers: Dict[str, str]">
598
+ <arg kind="POSITIONAL_OR_NAMED" required="true" repr="extra_headers: Dict[str, str]">
599
+ <name>extra_headers</name>
600
+ <type name="Dict" typedoc="dictionary">
601
+ <type name="str" typedoc="string"/>
602
+ <type name="str" typedoc="string"/>
603
+ </type>
604
+ </arg>
605
+ </arguments>
606
+ <doc>&lt;p&gt;Set the &lt;span class="name"&gt;extra_headers&lt;/span&gt; used in requests after the library is imported.&lt;/p&gt;
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>
608
+ <shortdoc>Set the `extra_headers` used in requests after the library is imported.</shortdoc>
609
+ </kw>
610
+ <kw name="Set Origin" lineno="668">
517
611
  <arguments repr="origin: str">
518
612
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="origin: str">
519
613
  <name>origin</name>
520
614
  <type name="str" typedoc="string"/>
521
615
  </arg>
522
616
  </arguments>
523
- <doc>&lt;p&gt;Update the &lt;span class="name"&gt;origin&lt;/span&gt; after the library is imported.&lt;/p&gt;
617
+ <doc>&lt;p&gt;Set the &lt;span class="name"&gt;origin&lt;/span&gt; after the library is imported.&lt;/p&gt;
524
618
  &lt;p&gt;This can be done during the &lt;span class="name"&gt;Suite setup&lt;/span&gt; when using DataDriver in situations where the OpenAPI document is available on disk but the target host address is not known before the test starts.&lt;/p&gt;
525
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>
526
- <shortdoc>Update the `origin` after the library is imported.</shortdoc>
620
+ <shortdoc>Set the `origin` after the library is imported.</shortdoc>
527
621
  </kw>
528
- <kw name="Validate Resource Properties" lineno="1871">
622
+ <kw name="Set Security Token" lineno="682">
623
+ <arguments repr="security_token: str">
624
+ <arg kind="POSITIONAL_OR_NAMED" required="true" repr="security_token: str">
625
+ <name>security_token</name>
626
+ <type name="str" typedoc="string"/>
627
+ </arg>
628
+ </arguments>
629
+ <doc>&lt;p&gt;Set the &lt;span class="name"&gt;security_token&lt;/span&gt; after the library is imported.&lt;/p&gt;
630
+ &lt;p&gt;After calling this keyword, subsequent requests will use the provided token.&lt;/p&gt;</doc>
631
+ <shortdoc>Set the `security_token` after the library is imported.</shortdoc>
632
+ </kw>
633
+ <kw name="Validate Resource Properties" lineno="2019">
529
634
  <arguments repr="resource: Dict[str, Any], schema: Dict[str, Any]">
530
635
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="resource: Dict[str, Any]">
531
636
  <name>resource</name>
@@ -545,7 +650,7 @@
545
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>
546
651
  <shortdoc>Validate that the `resource` does not contain any properties that are not defined in the `schema_properties`.</shortdoc>
547
652
  </kw>
548
- <kw name="Validate Response" lineno="1719">
653
+ <kw name="Validate Response" lineno="1852">
549
654
  <arguments repr="path: str, response: Response, original_data: Dict[str, Any] | None = None">
550
655
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
551
656
  <name>path</name>
@@ -578,7 +683,7 @@
578
683
  &lt;/ul&gt;</doc>
579
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>
580
685
  </kw>
581
- <kw name="Validate Send Response" lineno="1989">
686
+ <kw name="Validate Send Response" lineno="2137">
582
687
  <arguments repr="response: Response, original_data: Dict[str, Any] | None = None">
583
688
  <arg kind="POSITIONAL_OR_NAMED" required="true" repr="response: Response">
584
689
  <name>response</name>
@@ -649,6 +754,7 @@
649
754
  <usage>Get Json Data For Dto Class</usage>
650
755
  <usage>Get Json Data With Conflict</usage>
651
756
  <usage>Perform Validated Request</usage>
757
+ <usage>Set Extra Headers</usage>
652
758
  <usage>Validate Resource Properties</usage>
653
759
  <usage>Validate Response</usage>
654
760
  <usage>Validate Send Response</usage>
@@ -681,6 +787,7 @@
681
787
  <usage>Authorized Request</usage>
682
788
  <usage>Get Invalid Json Data</usage>
683
789
  <usage>Get Invalidated Parameters</usage>
790
+ <usage>Get Invalidated Url</usage>
684
791
  <usage>Get Json Data With Conflict</usage>
685
792
  <usage>Get Valid Id For Endpoint</usage>
686
793
  <usage>Perform Validated Request</usage>
@@ -746,7 +853,10 @@
746
853
  <usage>Get Valid Id For Endpoint</usage>
747
854
  <usage>Get Valid Url</usage>
748
855
  <usage>Perform Validated Request</usage>
856
+ <usage>Set Basic Auth</usage>
857
+ <usage>Set Extra Headers</usage>
749
858
  <usage>Set Origin</usage>
859
+ <usage>Set Security Token</usage>
750
860
  <usage>Validate Resource Properties</usage>
751
861
  <usage>Validate Response</usage>
752
862
  <usage>Validate Send Response</usage>
@@ -765,5 +875,20 @@
765
875
  <usage>Get Invalidated Parameters</usage>
766
876
  </usages>
767
877
  </type>
878
+ <type name="ValidationLevel" type="Enum">
879
+ <doc>&lt;p&gt;The available levels for the response_validation parameter.&lt;/p&gt;</doc>
880
+ <accepts>
881
+ <type>string</type>
882
+ </accepts>
883
+ <usages>
884
+ <usage>__init__</usage>
885
+ </usages>
886
+ <members>
887
+ <member name="DISABLED" value="DISABLED"/>
888
+ <member name="INFO" value="INFO"/>
889
+ <member name="WARN" value="WARN"/>
890
+ <member name="STRICT" value="STRICT"/>
891
+ </members>
892
+ </type>
768
893
  </typedocs>
769
894
  </keywordspec>
@@ -149,6 +149,7 @@ from openapi_core.contrib.requests import (
149
149
  RequestsOpenAPIResponse,
150
150
  )
151
151
  from openapi_core.exceptions import OpenAPIError
152
+ from openapi_core.templating.paths.exceptions import ServerNotFound
152
153
  from openapi_core.validation.exceptions import ValidationError
153
154
  from openapi_core.validation.response.exceptions import ResponseValidationError
154
155
  from openapi_core.validation.schemas.exceptions import InvalidSchemaValue
@@ -157,8 +158,8 @@ from prance.util.url import ResolutionError
157
158
  from requests import Response, Session
158
159
  from requests.auth import AuthBase, HTTPBasicAuth
159
160
  from requests.cookies import RequestsCookieJar as CookieJar
160
- from robot.api import Failure
161
161
  from robot.api.deco import keyword, library
162
+ from robot.api.exceptions import Failure
162
163
  from robot.libraries.BuiltIn import BuiltIn
163
164
 
164
165
  from OpenApiLibCore import value_utils
@@ -377,7 +378,15 @@ class RequestData:
377
378
 
378
379
  def get_required_properties_dict(self) -> Dict[str, Any]:
379
380
  """Get the json-compatible dto data containing only the required properties."""
380
- 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
+
381
390
  required_properties_dict: Dict[str, Any] = {}
382
391
  for key, value in (self.dto.as_dict()).items():
383
392
  if key in required_properties:
@@ -413,20 +422,38 @@ class RequestData:
413
422
 
414
423
  def get_required_params(self) -> Dict[str, str]:
415
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
+
416
433
  required_parameters = [
417
434
  p.get("name") for p in self.parameters if p.get("required")
418
435
  ]
436
+ required_parameters.extend(mandatory_parameters)
419
437
  return {k: v for k, v in self.params.items() if k in required_parameters}
420
438
 
421
439
  def get_required_headers(self) -> Dict[str, str]:
422
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
+
423
449
  required_parameters = [
424
450
  p.get("name") for p in self.parameters if p.get("required")
425
451
  ]
452
+ required_parameters.extend(mandatory_parameters)
426
453
  return {k: v for k, v in self.headers.items() if k in required_parameters}
427
454
 
428
455
 
429
- @library(scope="TEST SUITE", doc_format="ROBOT")
456
+ @library(scope="SUITE", doc_format="ROBOT")
430
457
  class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
431
458
  """
432
459
  Main class providing the keywords and core logic to interact with an OpenAPI server.
@@ -440,10 +467,13 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
440
467
  source: str,
441
468
  origin: str = "",
442
469
  base_path: str = "",
470
+ response_validation: ValidationLevel = ValidationLevel.WARN,
471
+ disable_server_validation: bool = True,
443
472
  mappings_path: Union[str, Path] = "",
444
473
  invalid_property_default_response: int = 422,
445
474
  default_id_property_name: str = "id",
446
475
  faker_locale: Optional[Union[str, List[str]]] = None,
476
+ require_body_for_invalid_url: bool = False,
447
477
  recursion_limit: int = 1,
448
478
  recursion_default: Any = {},
449
479
  username: str = "",
@@ -470,6 +500,25 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
470
500
  section in the openapi document.
471
501
  E.g. ``/petshop/v2``.
472
502
 
503
+ == Test case execution ==
504
+
505
+ === response_validation ===
506
+ By default, a ``WARN`` is logged when the Response received after a Request does not
507
+ comply with the schema as defined in the openapi document for the given operation. The
508
+ following values are supported:
509
+
510
+ - ``DISABLED``: All Response validation errors will be ignored
511
+ - ``INFO``: Any Response validation erros will be logged at ``INFO`` level
512
+ - ``WARN``: Any Response validation erros will be logged at ``WARN`` level
513
+ - ``STRICT``: The Test Case will fail on any Response validation errors
514
+
515
+ === disable_server_validation ===
516
+ If enabled by setting this parameter to ``True``, the Response validation will also
517
+ include possible errors for Requests made to a server address that is not defined in
518
+ the list of servers in the openapi document. This generally means that if there is a
519
+ mismatch, every Test Case will raise this error. Note that ``localhost`` and
520
+ ``127.0.0.1`` are not considered the same by Response validation.
521
+
473
522
  == API-specific configurations ==
474
523
 
475
524
  === mappings_path ===
@@ -496,6 +545,14 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
496
545
  A locale string or list of locale strings to pass to the Faker library to be
497
546
  used in generation of string data for supported format types.
498
547
 
548
+ === require_body_for_invalid_url ===
549
+ When a request is made against an invalid url, this usually is because of a "404" request;
550
+ a request for a resource that does not exist. Depending on API implementation, when a
551
+ request with a missing or invalid request body is made on a non-existent resource,
552
+ either a 404 or a 422 or 400 Response is normally returned. If the API being tested
553
+ processes the request body before checking if the requested resource exists, set
554
+ this parameter to True.
555
+
499
556
  == Parsing parameters ==
500
557
 
501
558
  === recursion_limit ===
@@ -552,6 +609,8 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
552
609
  self._source = source
553
610
  self._origin = origin
554
611
  self._base_path = base_path
612
+ self.response_validation = response_validation
613
+ self.disable_server_validation = disable_server_validation
555
614
  self._recursion_limit = recursion_limit
556
615
  self._recursion_default = recursion_default
557
616
  self.session = Session()
@@ -559,7 +618,7 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
559
618
  # if multiple are provided, username and password take precedence
560
619
  self.security_token = security_token
561
620
  self.auth = auth
562
- if username and password:
621
+ if username:
563
622
  self.auth = HTTPBasicAuth(username, password)
564
623
  # Robot Framework does not allow users to create tuples and requests
565
624
  # does not accept lists, so perform the conversion here
@@ -596,8 +655,10 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
596
655
  )
597
656
  if faker_locale:
598
657
  FAKE.set_locale(locale=faker_locale)
658
+ self.require_body_for_invalid_url = require_body_for_invalid_url
599
659
  # update the globally available DEFAULT_ID_PROPERTY_NAME to the provided value
600
660
  DEFAULT_ID_PROPERTY_NAME.id_property_name = default_id_property_name
661
+ self._server_validation_warning_logged = False
601
662
 
602
663
  @property
603
664
  def origin(self) -> str:
@@ -606,7 +667,7 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
606
667
  @keyword
607
668
  def set_origin(self, origin: str) -> None:
608
669
  """
609
- Update the `origin` after the library is imported.
670
+ Set the `origin` after the library is imported.
610
671
 
611
672
  This can be done during the `Suite setup` when using DataDriver in situations
612
673
  where the OpenAPI document is available on disk but the target host address is
@@ -617,6 +678,47 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
617
678
  """
618
679
  self._origin = origin
619
680
 
681
+ @keyword
682
+ def set_security_token(self, security_token: str) -> None:
683
+ """
684
+ Set the `security_token` after the library is imported.
685
+
686
+ After calling this keyword, subsequent requests will use the provided token.
687
+ """
688
+ self.security_token = security_token
689
+
690
+ @keyword
691
+ def set_basic_auth(self, username: str, password: str) -> None:
692
+ """
693
+ Set the `username` and `password` used for basic
694
+ authentication after the library is imported.
695
+
696
+ After calling this keyword, subsequent requests
697
+ will use the provided credentials.
698
+ """
699
+ if username:
700
+ self.auth = HTTPBasicAuth(username, password)
701
+
702
+ @keyword
703
+ def set_auth(self, auth: AuthBase) -> None:
704
+ """
705
+ Set the `auth` used for authentication after the library is imported.
706
+
707
+ After calling this keyword, subsequent requests
708
+ will use the provided `auth` instance.
709
+ """
710
+ self.auth = auth
711
+
712
+ @keyword
713
+ def set_extra_headers(self, extra_headers: Dict[str, str]) -> None:
714
+ """
715
+ Set the `extra_headers` used in requests after the library is imported.
716
+
717
+ After calling this keyword, subsequent requests
718
+ will use the provided `extra_headers`.
719
+ """
720
+ self.extra_headers = extra_headers
721
+
620
722
  @property
621
723
  def base_url(self) -> str:
622
724
  return f"{self.origin}{self._base_path}"
@@ -1251,13 +1353,32 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1251
1353
  return json_data
1252
1354
 
1253
1355
  @keyword
1254
- 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]:
1255
1363
  """
1256
1364
  Return an url with all the path parameters in the `valid_url` replaced by a
1257
- 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.
1258
1368
 
1259
1369
  Raises ValueError if the valid_url cannot be invalidated.
1260
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
1261
1382
  parameterized_endpoint = self.get_parameterized_endpoint_from_url(valid_url)
1262
1383
  parameterized_url = self.base_url + parameterized_endpoint
1263
1384
  valid_url_parts = list(reversed(valid_url.split("/")))
@@ -1301,6 +1422,9 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1301
1422
  """
1302
1423
  method = method.lower()
1303
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
+ ]
1304
1428
  if not data_relations:
1305
1429
  if not request_data.dto_schema:
1306
1430
  raise ValueError(
@@ -1611,10 +1735,17 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1611
1735
  params: Optional[Dict[str, Any]] = None,
1612
1736
  headers: Optional[Dict[str, str]] = None,
1613
1737
  json_data: Optional[JSON] = None,
1738
+ data: Any = None,
1739
+ files: Any = None,
1614
1740
  ) -> Response:
1615
1741
  """
1616
1742
  Perform a request using the security token or authentication set in the library.
1617
1743
 
1744
+ `json_data`, `data` and `files` are passed to `requests.request`s `json`,
1745
+ `data` and `files` parameters unaltered.
1746
+ See the requests documentation for details:
1747
+ https://requests.readthedocs.io/en/latest/api/#requests.request
1748
+
1618
1749
  > Note: provided username / password or auth objects take precedence over token
1619
1750
  based security
1620
1751
  """
@@ -1632,6 +1763,8 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1632
1763
  params=params,
1633
1764
  headers=headers,
1634
1765
  json=json_data,
1766
+ data=data,
1767
+ files=files,
1635
1768
  cookies=self.cookies,
1636
1769
  auth=self.auth,
1637
1770
  proxies=self.proxies,
@@ -1788,6 +1921,11 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1788
1921
  response_spec["content"][content_type]["schema"]
1789
1922
  )
1790
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
1791
1929
  response_type = response_schema.get("type", "undefined")
1792
1930
  if response_type not in ["object", "array"]:
1793
1931
  self._validate_value_type(value=json_response, expected_type=response_type)
@@ -1841,7 +1979,7 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1841
1979
  request=RequestsOpenAPIRequest(response.request),
1842
1980
  response=RequestsOpenAPIResponse(response),
1843
1981
  )
1844
- except ResponseValidationError as exception:
1982
+ except (ResponseValidationError, ServerNotFound) as exception:
1845
1983
  errors: List[InvalidSchemaValue] = exception.__cause__
1846
1984
  validation_errors: Optional[List[ValidationError]] = getattr(
1847
1985
  errors, "schema_errors", None
@@ -1856,6 +1994,16 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1856
1994
  else:
1857
1995
  error_message = str(exception)
1858
1996
 
1997
+ if isinstance(exception, ServerNotFound):
1998
+ if not self._server_validation_warning_logged:
1999
+ logger.warning(
2000
+ f"ServerNotFound was raised during response validation. "
2001
+ f"Due to this, no full response validation will be performed."
2002
+ f"\nThe original error was: {error_message}"
2003
+ )
2004
+ self._server_validation_warning_logged = True
2005
+ if self.disable_server_validation:
2006
+ return
1859
2007
  if response.status_code == self.invalid_property_default_response:
1860
2008
  logger.debug(error_message)
1861
2009
  return