robotframework-openapitools 0.3.0__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. OpenApiDriver/__init__.py +45 -41
  2. OpenApiDriver/openapi_executors.py +83 -49
  3. OpenApiDriver/openapi_reader.py +114 -116
  4. OpenApiDriver/openapidriver.libspec +209 -133
  5. OpenApiDriver/openapidriver.py +31 -296
  6. OpenApiLibCore/__init__.py +39 -13
  7. OpenApiLibCore/annotations.py +10 -0
  8. OpenApiLibCore/data_generation/__init__.py +10 -0
  9. OpenApiLibCore/data_generation/body_data_generation.py +250 -0
  10. OpenApiLibCore/data_generation/data_generation_core.py +233 -0
  11. OpenApiLibCore/data_invalidation.py +294 -0
  12. OpenApiLibCore/dto_base.py +75 -129
  13. OpenApiLibCore/dto_utils.py +125 -85
  14. OpenApiLibCore/localized_faker.py +88 -0
  15. OpenApiLibCore/models.py +723 -0
  16. OpenApiLibCore/oas_cache.py +14 -13
  17. OpenApiLibCore/openapi_libcore.libspec +363 -322
  18. OpenApiLibCore/openapi_libcore.py +388 -1903
  19. OpenApiLibCore/parameter_utils.py +97 -0
  20. OpenApiLibCore/path_functions.py +215 -0
  21. OpenApiLibCore/path_invalidation.py +42 -0
  22. OpenApiLibCore/protocols.py +38 -0
  23. OpenApiLibCore/request_data.py +246 -0
  24. OpenApiLibCore/resource_relations.py +55 -0
  25. OpenApiLibCore/validation.py +380 -0
  26. OpenApiLibCore/value_utils.py +216 -481
  27. openapi_libgen/__init__.py +3 -0
  28. openapi_libgen/command_line.py +75 -0
  29. openapi_libgen/generator.py +82 -0
  30. openapi_libgen/parsing_utils.py +30 -0
  31. openapi_libgen/spec_parser.py +154 -0
  32. openapi_libgen/templates/__init__.jinja +3 -0
  33. openapi_libgen/templates/library.jinja +30 -0
  34. robotframework_openapitools-1.0.0.dist-info/METADATA +249 -0
  35. robotframework_openapitools-1.0.0.dist-info/RECORD +40 -0
  36. {robotframework_openapitools-0.3.0.dist-info → robotframework_openapitools-1.0.0.dist-info}/WHEEL +1 -1
  37. robotframework_openapitools-1.0.0.dist-info/entry_points.txt +3 -0
  38. roboswag/__init__.py +0 -9
  39. roboswag/__main__.py +0 -3
  40. roboswag/auth.py +0 -44
  41. roboswag/cli.py +0 -80
  42. roboswag/core.py +0 -85
  43. roboswag/generate/__init__.py +0 -1
  44. roboswag/generate/generate.py +0 -121
  45. roboswag/generate/models/__init__.py +0 -0
  46. roboswag/generate/models/api.py +0 -219
  47. roboswag/generate/models/definition.py +0 -28
  48. roboswag/generate/models/endpoint.py +0 -68
  49. roboswag/generate/models/parameter.py +0 -25
  50. roboswag/generate/models/response.py +0 -8
  51. roboswag/generate/models/tag.py +0 -16
  52. roboswag/generate/models/utils.py +0 -60
  53. roboswag/generate/templates/api_init.jinja +0 -15
  54. roboswag/generate/templates/models.jinja +0 -7
  55. roboswag/generate/templates/paths.jinja +0 -68
  56. roboswag/logger.py +0 -33
  57. roboswag/validate/__init__.py +0 -6
  58. roboswag/validate/core.py +0 -3
  59. roboswag/validate/schema.py +0 -21
  60. roboswag/validate/text_response.py +0 -14
  61. robotframework_openapitools-0.3.0.dist-info/METADATA +0 -41
  62. robotframework_openapitools-0.3.0.dist-info/RECORD +0 -41
  63. {robotframework_openapitools-0.3.0.dist-info → robotframework_openapitools-1.0.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,294 @@
1
+ """
2
+ Module holding the functions related to invalidation of valid data (generated
3
+ to make 2xx requests) to support testing for 4xx responses.
4
+ """
5
+
6
+ from copy import deepcopy
7
+ from random import choice
8
+ from typing import Any
9
+
10
+ from requests import Response
11
+ from robot.api import logger
12
+ from robot.libraries.BuiltIn import BuiltIn
13
+
14
+ from OpenApiLibCore.annotations import JSON
15
+ from OpenApiLibCore.dto_base import (
16
+ NOT_SET,
17
+ Dto,
18
+ IdReference,
19
+ PropertyValueConstraint,
20
+ UniquePropertyValueConstraint,
21
+ )
22
+ from OpenApiLibCore.models import ParameterObject, UnionTypeSchema
23
+ from OpenApiLibCore.request_data import RequestData
24
+ from OpenApiLibCore.value_utils import IGNORE, get_invalid_value
25
+
26
+ run_keyword = BuiltIn().run_keyword
27
+
28
+
29
+ def get_invalid_body_data(
30
+ url: str,
31
+ method: str,
32
+ status_code: int,
33
+ request_data: RequestData,
34
+ invalid_property_default_response: int,
35
+ ) -> dict[str, Any]:
36
+ method = method.lower()
37
+ data_relations = request_data.dto.get_body_relations_for_error_code(status_code)
38
+ if not data_relations:
39
+ if request_data.body_schema is None:
40
+ raise ValueError(
41
+ "Failed to invalidate: request_data does not contain a body_schema."
42
+ )
43
+ json_data = request_data.dto.get_invalidated_data(
44
+ schema=request_data.body_schema,
45
+ status_code=status_code,
46
+ invalid_property_default_code=invalid_property_default_response,
47
+ )
48
+ return json_data
49
+ resource_relation = choice(data_relations)
50
+ if isinstance(resource_relation, UniquePropertyValueConstraint):
51
+ json_data = run_keyword(
52
+ "get_json_data_with_conflict",
53
+ url,
54
+ method,
55
+ request_data.dto,
56
+ status_code,
57
+ )
58
+ elif isinstance(resource_relation, IdReference):
59
+ run_keyword("ensure_in_use", url, resource_relation)
60
+ json_data = request_data.dto.as_dict()
61
+ else:
62
+ if request_data.body_schema is None:
63
+ raise ValueError(
64
+ "Failed to invalidate: request_data does not contain a body_schema."
65
+ )
66
+ json_data = request_data.dto.get_invalidated_data(
67
+ schema=request_data.body_schema,
68
+ status_code=status_code,
69
+ invalid_property_default_code=invalid_property_default_response,
70
+ )
71
+ return json_data
72
+
73
+
74
+ def get_invalidated_parameters(
75
+ status_code: int, request_data: RequestData, invalid_property_default_response: int
76
+ ) -> tuple[dict[str, JSON], dict[str, JSON]]:
77
+ if not request_data.parameters:
78
+ raise ValueError("No params or headers to invalidate.")
79
+
80
+ # ensure the status_code can be triggered
81
+ relations = request_data.dto.get_parameter_relations_for_error_code(status_code)
82
+ relations_for_status_code = [
83
+ r
84
+ for r in relations
85
+ if isinstance(r, PropertyValueConstraint)
86
+ and (status_code in (r.error_code, r.invalid_value_error_code))
87
+ ]
88
+ parameters_to_ignore = {
89
+ r.property_name
90
+ for r in relations_for_status_code
91
+ if r.invalid_value_error_code == status_code and r.invalid_value == IGNORE
92
+ }
93
+ relation_property_names = {r.property_name for r in relations_for_status_code}
94
+ if not relation_property_names:
95
+ if status_code != invalid_property_default_response:
96
+ raise ValueError(f"No relations to cause status_code {status_code} found.")
97
+
98
+ # ensure we're not modifying mutable properties
99
+ params = deepcopy(request_data.params)
100
+ headers = deepcopy(request_data.headers)
101
+
102
+ if status_code == invalid_property_default_response:
103
+ # take the params and headers that can be invalidated based on data type
104
+ # and expand the set with properties that can be invalided by relations
105
+ parameter_names = set(request_data.params_that_can_be_invalidated).union(
106
+ request_data.headers_that_can_be_invalidated
107
+ )
108
+ parameter_names.update(relation_property_names)
109
+ if not parameter_names:
110
+ raise ValueError(
111
+ "None of the query parameters and headers can be invalidated."
112
+ )
113
+ else:
114
+ # non-default status_codes can only be the result of a Relation
115
+ parameter_names = relation_property_names
116
+
117
+ # Dto mappings may contain generic mappings for properties that are not present
118
+ # in this specific schema
119
+ request_data_parameter_names = [p.name for p in request_data.parameters]
120
+ additional_relation_property_names = {
121
+ n for n in relation_property_names if n not in request_data_parameter_names
122
+ }
123
+ if additional_relation_property_names:
124
+ logger.warn(
125
+ f"get_parameter_relations_for_error_code yielded properties that are "
126
+ f"not defined in the schema: {additional_relation_property_names}\n"
127
+ f"These properties will be ignored for parameter invalidation."
128
+ )
129
+ parameter_names = parameter_names - additional_relation_property_names
130
+
131
+ if not parameter_names:
132
+ raise ValueError(
133
+ f"No parameter can be changed to cause status_code {status_code}."
134
+ )
135
+
136
+ parameter_names = parameter_names - parameters_to_ignore
137
+ parameter_to_invalidate = choice(tuple(parameter_names))
138
+
139
+ # check for invalid parameters in the provided request_data
140
+ try:
141
+ [parameter_data] = [
142
+ data
143
+ for data in request_data.parameters
144
+ if data.name == parameter_to_invalidate
145
+ ]
146
+ except Exception:
147
+ raise ValueError(
148
+ f"{parameter_to_invalidate} not found in provided parameters."
149
+ ) from None
150
+
151
+ # get the invalid_value for the chosen parameter
152
+ try:
153
+ [invalid_value_for_error_code] = [
154
+ r.invalid_value
155
+ for r in relations_for_status_code
156
+ if r.property_name == parameter_to_invalidate
157
+ and r.invalid_value_error_code == status_code
158
+ ]
159
+ except ValueError:
160
+ invalid_value_for_error_code = NOT_SET
161
+
162
+ # get the constraint values if available for the chosen parameter
163
+ try:
164
+ [values_from_constraint] = [
165
+ r.values
166
+ for r in relations_for_status_code
167
+ if r.property_name == parameter_to_invalidate
168
+ ]
169
+ except ValueError:
170
+ values_from_constraint = []
171
+
172
+ # if the parameter was not provided, add it to params / headers
173
+ params, headers = ensure_parameter_in_parameters(
174
+ parameter_to_invalidate=parameter_to_invalidate,
175
+ params=params,
176
+ headers=headers,
177
+ parameter_data=parameter_data,
178
+ values_from_constraint=values_from_constraint,
179
+ )
180
+
181
+ # determine the invalid_value
182
+ if invalid_value_for_error_code != NOT_SET:
183
+ invalid_value = invalid_value_for_error_code
184
+ else:
185
+ if parameter_to_invalidate in params.keys():
186
+ valid_value = params[parameter_to_invalidate]
187
+ else:
188
+ valid_value = headers[parameter_to_invalidate]
189
+
190
+ value_schema = parameter_data.schema_
191
+ if value_schema is None:
192
+ raise ValueError(f"No schema defined for parameter: {parameter_data}.")
193
+
194
+ if isinstance(value_schema, UnionTypeSchema):
195
+ # FIXME: extra handling may be needed in case of values_from_constraint
196
+ value_schema = choice(value_schema.resolved_schemas)
197
+
198
+ invalid_value = get_invalid_value(
199
+ value_schema=value_schema,
200
+ current_value=valid_value,
201
+ values_from_constraint=values_from_constraint,
202
+ )
203
+ logger.debug(f"{parameter_to_invalidate} changed to {invalid_value}")
204
+
205
+ # update the params / headers and return
206
+ if parameter_to_invalidate in params.keys():
207
+ params[parameter_to_invalidate] = invalid_value
208
+ else:
209
+ headers[parameter_to_invalidate] = str(invalid_value)
210
+ return params, headers
211
+
212
+
213
+ def ensure_parameter_in_parameters(
214
+ parameter_to_invalidate: str,
215
+ params: dict[str, JSON],
216
+ headers: dict[str, JSON],
217
+ parameter_data: ParameterObject,
218
+ values_from_constraint: list[JSON],
219
+ ) -> tuple[dict[str, JSON], dict[str, JSON]]:
220
+ """
221
+ Returns the params, headers tuple with parameter_to_invalidate with a valid
222
+ value to params or headers if not originally present.
223
+ """
224
+ if (
225
+ parameter_to_invalidate not in params.keys()
226
+ and parameter_to_invalidate not in headers.keys()
227
+ ):
228
+ if values_from_constraint:
229
+ valid_value = choice(values_from_constraint)
230
+ else:
231
+ value_schema = parameter_data.schema_
232
+ if value_schema is None:
233
+ raise ValueError(f"No schema defined for parameter: {parameter_data}.")
234
+
235
+ if isinstance(value_schema, UnionTypeSchema):
236
+ value_schema = choice(value_schema.resolved_schemas)
237
+ valid_value = value_schema.get_valid_value()
238
+ if (
239
+ parameter_data.in_ == "query"
240
+ and parameter_to_invalidate not in params.keys()
241
+ ):
242
+ params[parameter_to_invalidate] = valid_value
243
+ if (
244
+ parameter_data.in_ == "header"
245
+ and parameter_to_invalidate not in headers.keys()
246
+ ):
247
+ headers[parameter_to_invalidate] = str(valid_value)
248
+ return params, headers
249
+
250
+
251
+ def get_json_data_with_conflict(
252
+ url: str, base_url: str, method: str, dto: Dto, conflict_status_code: int
253
+ ) -> dict[str, Any]:
254
+ method = method.lower()
255
+ json_data = dto.as_dict()
256
+ unique_property_value_constraints = [
257
+ r for r in dto.get_relations() if isinstance(r, UniquePropertyValueConstraint)
258
+ ]
259
+ for relation in unique_property_value_constraints:
260
+ json_data[relation.property_name] = relation.value
261
+ # create a new resource that the original request will conflict with
262
+ if method in ["patch", "put"]:
263
+ post_url_parts = url.split("/")[:-1]
264
+ post_url = "/".join(post_url_parts)
265
+ # the PATCH or PUT may use a different dto than required for POST
266
+ # so a valid POST dto must be constructed
267
+ path = post_url.replace(base_url, "")
268
+ request_data: RequestData = run_keyword("get_request_data", path, "post")
269
+ post_json = request_data.dto.as_dict()
270
+ for key in post_json.keys():
271
+ if key in json_data:
272
+ post_json[key] = json_data.get(key)
273
+ else:
274
+ post_url = url
275
+ post_json = json_data
276
+ path = post_url.replace(base_url, "")
277
+ request_data = run_keyword("get_request_data", path, "post")
278
+
279
+ response: Response = run_keyword(
280
+ "authorized_request",
281
+ post_url,
282
+ "post",
283
+ request_data.params,
284
+ request_data.headers,
285
+ post_json,
286
+ )
287
+ # conflicting resource may already exist
288
+ assert response.ok or response.status_code == conflict_status_code, (
289
+ f"get_json_data_with_conflict received {response.status_code}: {response.json()}"
290
+ )
291
+ return json_data
292
+ raise ValueError(
293
+ f"No UniquePropertyValueConstraint in the get_relations list on dto {dto}."
294
+ )
@@ -5,88 +5,22 @@ test and constraints / restrictions on properties of the resources.
5
5
  """
6
6
 
7
7
  from abc import ABC
8
- from copy import deepcopy
9
8
  from dataclasses import dataclass, fields
10
- from logging import getLogger
11
9
  from random import choice, shuffle
12
- from typing import Any, Dict, List, Optional, Union
10
+ from typing import Any
13
11
  from uuid import uuid4
14
12
 
15
- from OpenApiLibCore import value_utils
13
+ from robot.api import logger
16
14
 
17
- logger = getLogger(__name__)
15
+ from OpenApiLibCore import value_utils
16
+ from OpenApiLibCore.models import NullSchema, ObjectSchema, UnionTypeSchema
17
+ from OpenApiLibCore.parameter_utils import get_oas_name_from_safe_name
18
18
 
19
19
  NOT_SET = object()
20
20
  SENTINEL = object()
21
21
 
22
22
 
23
- def resolve_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
24
- """
25
- Helper function to resolve allOf, anyOf and oneOf instances in a schema.
26
-
27
- The schemas are used to generate values for headers, query parameters and json
28
- bodies to be able to make requests.
29
- """
30
- # Schema is mutable, so deepcopy to prevent mutation of original schema argument
31
- resolved_schema = deepcopy(schema)
32
-
33
- # allOf / anyOf / oneOf may be nested, so recursively resolve the dict-typed values
34
- for key, value in resolved_schema.items():
35
- if isinstance(value, dict):
36
- resolved_schema[key] = resolve_schema(value)
37
-
38
- # When handling allOf there should no duplicate keys, so the schema parts can
39
- # just be merged after resolving the individual parts
40
- if schema_parts := resolved_schema.pop("allOf", None):
41
- for schema_part in schema_parts:
42
- resolved_part = resolve_schema(schema_part)
43
- resolved_schema = merge_schemas(resolved_schema, resolved_part)
44
- # Handling anyOf and oneOf requires extra logic to deal with the "type" information.
45
- # Some properties / parameters may be of different types and each type may have its
46
- # own restrictions e.g. a parameter that accepts an enum value (string) or an
47
- # integer value within a certain range.
48
- # Since the library needs all this information for different purposes, the
49
- # schema_parts cannot be merged, so a helper property / key "types" is introduced.
50
- any_of = resolved_schema.pop("anyOf", [])
51
- one_of = resolved_schema.pop("oneOf", [])
52
- schema_parts = any_of if any_of else one_of
53
-
54
- for schema_part in schema_parts:
55
- resolved_part = resolve_schema(schema_part)
56
- if isinstance(resolved_part, dict) and "type" in resolved_part.keys():
57
- if "types" in resolved_schema.keys():
58
- resolved_schema["types"].append(resolved_part)
59
- else:
60
- resolved_schema["types"] = [resolved_part]
61
- else:
62
- resolved_schema = merge_schemas(resolved_schema, resolved_part)
63
-
64
- return resolved_schema
65
-
66
-
67
- def merge_schemas(first: Dict[str, Any], second: Dict[str, Any]) -> Dict[str, Any]:
68
- """Helper method to merge two schemas, recursively."""
69
- merged_schema = deepcopy(first)
70
- for key, value in second.items():
71
- # for existing keys, merge dict and list values, leave others unchanged
72
- if key in merged_schema.keys():
73
- if isinstance(value, dict):
74
- # if the key holds a dict, merge the values (e.g. 'properties')
75
- merged_schema[key].update(value)
76
- elif isinstance(value, list):
77
- # if the key holds a list, extend the values (e.g. 'required')
78
- merged_schema[key].extend(value)
79
- elif value != merged_schema[key]:
80
- logger.debug(
81
- f"key '{key}' with value '{merged_schema[key]}'"
82
- f" not updated to '{value}'"
83
- )
84
- else:
85
- merged_schema[key] = value
86
- return merged_schema
87
-
88
-
89
- class ResourceRelation(ABC): # pylint: disable=too-few-public-methods
23
+ class ResourceRelation(ABC):
90
24
  """ABC for all resource relations or restrictions within the API."""
91
25
 
92
26
  property_name: str
@@ -95,10 +29,12 @@ class ResourceRelation(ABC): # pylint: disable=too-few-public-methods
95
29
 
96
30
  @dataclass
97
31
  class PathPropertiesConstraint(ResourceRelation):
98
- """The resolved path for the endpoint."""
32
+ """The value to be used as the ``path`` for related requests."""
99
33
 
100
34
  path: str
101
35
  property_name: str = "id"
36
+ invalid_value: Any = NOT_SET
37
+ invalid_value_error_code: int = 422
102
38
  error_code: int = 404
103
39
 
104
40
 
@@ -107,10 +43,11 @@ class PropertyValueConstraint(ResourceRelation):
107
43
  """The allowed values for property_name."""
108
44
 
109
45
  property_name: str
110
- values: List[Any]
46
+ values: list[Any]
111
47
  invalid_value: Any = NOT_SET
112
48
  invalid_value_error_code: int = 422
113
49
  error_code: int = 422
50
+ treat_as_mandatory: bool = False
114
51
 
115
52
 
116
53
  @dataclass
@@ -119,7 +56,7 @@ class IdDependency(ResourceRelation):
119
56
 
120
57
  property_name: str
121
58
  get_path: str
122
- operation_id: Optional[str] = None
59
+ operation_id: str = ""
123
60
  error_code: int = 422
124
61
 
125
62
 
@@ -141,27 +78,40 @@ class UniquePropertyValueConstraint(ResourceRelation):
141
78
  error_code: int = 422
142
79
 
143
80
 
144
- Relation = Union[
145
- IdDependency,
146
- IdReference,
147
- PathPropertiesConstraint,
148
- PropertyValueConstraint,
149
- UniquePropertyValueConstraint,
150
- ]
151
-
152
-
153
81
  @dataclass
154
82
  class Dto(ABC):
155
83
  """Base class for the Dto class."""
156
84
 
157
85
  @staticmethod
158
- def get_parameter_relations() -> List[Relation]:
86
+ def get_path_relations() -> list[PathPropertiesConstraint]:
87
+ """Return the list of Relations for the header and query parameters."""
88
+ return []
89
+
90
+ def get_path_relations_for_error_code(
91
+ self, error_code: int
92
+ ) -> list[PathPropertiesConstraint]:
93
+ """Return the list of Relations associated with the given error_code."""
94
+ relations: list[PathPropertiesConstraint] = [
95
+ r
96
+ for r in self.get_path_relations()
97
+ if r.error_code == error_code
98
+ or (
99
+ getattr(r, "invalid_value_error_code", None) == error_code
100
+ and getattr(r, "invalid_value", None) != NOT_SET
101
+ )
102
+ ]
103
+ return relations
104
+
105
+ @staticmethod
106
+ def get_parameter_relations() -> list[ResourceRelation]:
159
107
  """Return the list of Relations for the header and query parameters."""
160
108
  return []
161
109
 
162
- def get_parameter_relations_for_error_code(self, error_code: int) -> List[Relation]:
110
+ def get_parameter_relations_for_error_code(
111
+ self, error_code: int
112
+ ) -> list[ResourceRelation]:
163
113
  """Return the list of Relations associated with the given error_code."""
164
- relations: List[Relation] = [
114
+ relations: list[ResourceRelation] = [
165
115
  r
166
116
  for r in self.get_parameter_relations()
167
117
  if r.error_code == error_code
@@ -173,13 +123,18 @@ class Dto(ABC):
173
123
  return relations
174
124
 
175
125
  @staticmethod
176
- def get_relations() -> List[Relation]:
126
+ def get_relations() -> list[ResourceRelation]:
177
127
  """Return the list of Relations for the (json) body."""
178
128
  return []
179
129
 
180
- def get_relations_for_error_code(self, error_code: int) -> List[Relation]:
181
- """Return the list of Relations associated with the given error_code."""
182
- relations: List[Relation] = [
130
+ def get_body_relations_for_error_code(
131
+ self, error_code: int
132
+ ) -> list[ResourceRelation]:
133
+ """
134
+ Return the list of Relations associated with the given error_code that are
135
+ applicable to the body / payload of the request.
136
+ """
137
+ relations: list[ResourceRelation] = [
183
138
  r
184
139
  for r in self.get_relations()
185
140
  if r.error_code == error_code
@@ -192,33 +147,25 @@ class Dto(ABC):
192
147
 
193
148
  def get_invalidated_data(
194
149
  self,
195
- schema: Dict[str, Any],
150
+ schema: ObjectSchema,
196
151
  status_code: int,
197
152
  invalid_property_default_code: int,
198
- ) -> Dict[str, Any]:
153
+ ) -> dict[str, Any]:
199
154
  """Return a data set with one of the properties set to an invalid value or type."""
200
- properties: Dict[str, Any] = self.as_dict()
155
+ properties: dict[str, Any] = self.as_dict()
201
156
 
202
- schema = resolve_schema(schema)
203
-
204
- relations = self.get_relations_for_error_code(error_code=status_code)
205
- # filter PathProperyConstraints since in that case no data can be invalidated
206
- relations = [
207
- r for r in relations if not isinstance(r, PathPropertiesConstraint)
208
- ]
157
+ relations = self.get_body_relations_for_error_code(error_code=status_code)
209
158
  property_names = [r.property_name for r in relations]
210
159
  if status_code == invalid_property_default_code:
211
160
  # add all properties defined in the schema, including optional properties
212
- property_names.extend((schema["properties"].keys()))
213
- # remove duplicates
214
- property_names = list(set(property_names))
161
+ property_names.extend((schema.properties.root.keys())) # type: ignore[union-attr]
215
162
  if not property_names:
216
163
  raise ValueError(
217
164
  f"No property can be invalidated to cause status_code {status_code}"
218
165
  )
219
- # shuffle the property_names so different properties on the Dto are invalidated
220
- # when rerunning the test
221
- shuffle(property_names)
166
+ # Remove duplicates, then shuffle the property_names so different properties on
167
+ # the Dto are invalidated when rerunning the test.
168
+ shuffle(list(set(property_names)))
222
169
  for property_name in property_names:
223
170
  # if possible, invalidate a constraint but send otherwise valid data
224
171
  id_dependencies = [
@@ -227,12 +174,12 @@ class Dto(ABC):
227
174
  if isinstance(r, IdDependency) and r.property_name == property_name
228
175
  ]
229
176
  if id_dependencies:
230
- invalid_value = uuid4().hex
177
+ invalid_id = uuid4().hex
231
178
  logger.debug(
232
- f"Breaking IdDependency for status_code {status_code}: replacing "
233
- f"{properties[property_name]} with {invalid_value}"
179
+ f"Breaking IdDependency for status_code {status_code}: setting "
180
+ f"{property_name} to {invalid_id}"
234
181
  )
235
- properties[property_name] = invalid_value
182
+ properties[property_name] = invalid_id
236
183
  return properties
237
184
 
238
185
  invalid_value_from_constraint = [
@@ -253,31 +200,30 @@ class Dto(ABC):
253
200
  )
254
201
  return properties
255
202
 
256
- value_schema = schema["properties"][property_name]
257
- value_schema = resolve_schema(value_schema)
258
-
259
- # Filter "type": "null" from the possible types since this indicates an
260
- # optional / nullable property that can only be invalidated by sending
261
- # invalid data of a non-null type
262
- if value_schemas := value_schema.get("types"):
263
- if len(value_schemas) > 1:
264
- value_schemas = [
265
- schema for schema in value_schemas if schema["type"] != "null"
266
- ]
267
- value_schema = choice(value_schemas)
203
+ value_schema = schema.properties.root[property_name] # type: ignore[union-attr]
204
+ if isinstance(value_schema, UnionTypeSchema):
205
+ # Filter "type": "null" from the possible types since this indicates an
206
+ # optional / nullable property that can only be invalidated by sending
207
+ # invalid data of a non-null type
208
+ non_null_schemas = [
209
+ s
210
+ for s in value_schema.resolved_schemas
211
+ if not isinstance(s, NullSchema)
212
+ ]
213
+ value_schema = choice(non_null_schemas)
268
214
 
269
215
  # there may not be a current_value when invalidating an optional property
270
216
  current_value = properties.get(property_name, SENTINEL)
271
217
  if current_value is SENTINEL:
272
218
  # the current_value isn't very relevant as long as the type is correct
273
219
  # so no logic to handle Relations / objects / arrays here
274
- property_type = value_schema["type"]
220
+ property_type = value_schema.type
275
221
  if property_type == "object":
276
222
  current_value = {}
277
223
  elif property_type == "array":
278
224
  current_value = []
279
225
  else:
280
- current_value = value_utils.get_valid_value(value_schema)
226
+ current_value = value_schema.get_valid_value()
281
227
 
282
228
  values_from_constraint = [
283
229
  r.values[0]
@@ -293,14 +239,14 @@ class Dto(ABC):
293
239
  )
294
240
  properties[property_name] = invalid_value
295
241
  logger.debug(
296
- f"Property {property_name} changed to {invalid_value} (received from "
242
+ f"Property {property_name} changed to {invalid_value!r} (received from "
297
243
  f"get_invalid_value)"
298
244
  )
299
245
  return properties
300
- logger.warning("get_invalidated_data returned unchanged properties")
246
+ logger.warn("get_invalidated_data returned unchanged properties")
301
247
  return properties # pragma: no cover
302
248
 
303
- def as_dict(self) -> Dict[Any, Any]:
249
+ def as_dict(self) -> dict[Any, Any]:
304
250
  """Return the dict representation of the Dto."""
305
251
  result = {}
306
252
 
@@ -308,7 +254,7 @@ class Dto(ABC):
308
254
  field_name = field.name
309
255
  if field_name not in self.__dict__:
310
256
  continue
311
- original_name = field.metadata["original_property_name"]
257
+ original_name = get_oas_name_from_safe_name(field_name)
312
258
  result[original_name] = getattr(self, field_name)
313
259
 
314
260
  return result