Skip to content

Commit f06d3cc

Browse files
committed
Test Endpoint
#3
1 parent 7fc8492 commit f06d3cc

File tree

2 files changed

+229
-45
lines changed

2 files changed

+229
-45
lines changed

openapi_python_client/openapi_parser/openapi.py

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from dataclasses import dataclass, field
3+
from dataclasses import dataclass, field, replace
44
from enum import Enum
55
from typing import Any, Dict, Generator, Iterable, List, Optional, Set
66

@@ -52,14 +52,14 @@ class Endpoint:
5252
method: str
5353
description: Optional[str]
5454
name: str
55-
query_parameters: List[Property]
56-
path_parameters: List[Property]
57-
responses: List[Response]
5855
requires_security: bool
59-
form_body_reference: Optional[Reference]
60-
json_body: Optional[Property]
6156
tag: str
62-
relative_imports: Set[str]
57+
relative_imports: Set[str] = field(default_factory=set)
58+
query_parameters: List[Property] = field(default_factory=list)
59+
path_parameters: List[Property] = field(default_factory=list)
60+
responses: List[Response] = field(default_factory=list)
61+
form_body_reference: Optional[Reference] = None
62+
json_body: Optional[Property] = None
6363

6464
@staticmethod
6565
def parse_request_form_body(body: Dict[str, Any], /) -> Optional[Reference]:
@@ -79,62 +79,61 @@ def parse_request_json_body(body: Dict[str, Any], /) -> Optional[Property]:
7979
return property_from_dict("json_body", required=True, data=json_body["schema"])
8080
return None
8181

82-
@staticmethod
83-
def from_data(*, data: Dict[str, Any], path: str, method: str) -> Endpoint:
84-
""" Construct an endpoint from the OpenAPI data """
85-
query_parameters: List[Property] = []
86-
path_parameters: List[Property] = []
87-
responses: List[Response] = []
88-
tag = data.get("tags", ["default"])[0]
89-
relative_imports: Set[str] = set()
82+
def _add_body(self, data: Dict[str, Any]) -> None:
83+
""" Adds form or JSON body to Endpoint if included in data """
84+
if "requestBody" not in data:
85+
return
86+
87+
self.form_body_reference = Endpoint.parse_request_form_body(data["requestBody"])
88+
self.json_body = Endpoint.parse_request_json_body(data["requestBody"])
89+
90+
if self.form_body_reference:
91+
self.relative_imports.add(import_string_from_reference(self.form_body_reference, prefix="..models"))
92+
if (
93+
self.json_body is not None
94+
and isinstance(self.json_body, (ListProperty, RefProperty, EnumProperty))
95+
and self.json_body.reference is not None
96+
):
97+
self.relative_imports.add(import_string_from_reference(self.json_body.reference, prefix="..models"))
98+
99+
def _add_responses(self, data: Dict[str, Any]) -> None:
100+
for code, response_dict in data["responses"].items():
101+
response = response_from_dict(status_code=int(code), data=response_dict)
102+
if isinstance(response, (RefResponse, ListRefResponse)):
103+
self.relative_imports.add(import_string_from_reference(response.reference, prefix="..models"))
104+
self.responses.append(response)
90105

106+
def _add_parameters(self, data: Dict[str, Any]) -> None:
91107
for param_dict in data.get("parameters", []):
92108
prop = property_from_dict(
93109
name=param_dict["name"], required=param_dict["required"], data=param_dict["schema"]
94110
)
95111
if isinstance(prop, (ListProperty, RefProperty, EnumProperty)) and prop.reference:
96-
relative_imports.add(import_string_from_reference(prop.reference, prefix="..models"))
112+
self.relative_imports.add(import_string_from_reference(prop.reference, prefix="..models"))
97113
if param_dict["in"] == ParameterLocation.QUERY:
98-
query_parameters.append(prop)
114+
self.query_parameters.append(prop)
99115
elif param_dict["in"] == ParameterLocation.PATH:
100-
path_parameters.append(prop)
116+
self.path_parameters.append(prop)
101117
else:
102118
raise ValueError(f"Don't know where to put this parameter: {param_dict}")
103119

104-
for code, response_dict in data["responses"].items():
105-
response = response_from_dict(status_code=int(code), data=response_dict)
106-
if isinstance(response, (RefResponse, ListRefResponse)):
107-
relative_imports.add(import_string_from_reference(response.reference, prefix="..models"))
108-
responses.append(response)
109-
form_body_reference = None
110-
json_body = None
111-
if "requestBody" in data:
112-
form_body_reference = Endpoint.parse_request_form_body(data["requestBody"])
113-
json_body = Endpoint.parse_request_json_body(data["requestBody"])
114-
115-
if form_body_reference:
116-
relative_imports.add(import_string_from_reference(form_body_reference, prefix="..models"))
117-
if (
118-
json_body is not None
119-
and isinstance(json_body, (ListProperty, RefProperty, EnumProperty))
120-
and json_body.reference is not None
121-
):
122-
relative_imports.add(import_string_from_reference(json_body.reference, prefix="..models"))
120+
@staticmethod
121+
def from_data(*, data: Dict[str, Any], path: str, method: str) -> Endpoint:
122+
""" Construct an endpoint from the OpenAPI data """
123123

124-
return Endpoint(
124+
endpoint = Endpoint(
125125
path=path,
126126
method=method,
127127
description=data.get("description"),
128128
name=data["operationId"],
129-
query_parameters=query_parameters,
130-
path_parameters=path_parameters,
131-
responses=responses,
132-
form_body_reference=form_body_reference,
133-
json_body=json_body,
134129
requires_security=bool(data.get("security")),
135-
tag=tag,
136-
relative_imports=relative_imports,
130+
tag=data.get("tags", ["default"])[0],
137131
)
132+
endpoint._add_parameters(data)
133+
endpoint._add_responses(data)
134+
endpoint._add_body(data)
135+
136+
return endpoint
138137

139138

140139
@dataclass

tests/test_openapi_parser/test_openapi.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,191 @@ def test_parse_request_json_body_no_data(self):
226226

227227
assert result is None
228228

229+
def test_add_body_no_data(self, mocker):
230+
from openapi_python_client.openapi_parser.openapi import Endpoint
231+
232+
parse_request_form_body = mocker.patch.object(Endpoint, "parse_request_form_body")
233+
endpoint = Endpoint(
234+
path="path",
235+
method="method",
236+
description=None,
237+
name="name",
238+
requires_security=False,
239+
tag="tag",
240+
relative_imports={"import_3"},
241+
)
242+
243+
endpoint._add_body({})
244+
245+
parse_request_form_body.assert_not_called()
246+
247+
def test_add_body_happy(self, mocker):
248+
from openapi_python_client.openapi_parser.openapi import Endpoint, Reference, RefProperty
249+
250+
request_body = mocker.MagicMock()
251+
form_body_reference = Reference(ref="a")
252+
parse_request_form_body = mocker.patch.object(
253+
Endpoint, "parse_request_form_body", return_value=form_body_reference
254+
)
255+
json_body = RefProperty(name="name", required=True, default=None, reference=Reference("b"))
256+
parse_request_json_body = mocker.patch.object(Endpoint, "parse_request_json_body", return_value=json_body)
257+
import_string_from_reference = mocker.patch(
258+
f"{MODULE_NAME}.import_string_from_reference", side_effect=["import_1", "import_2"]
259+
)
260+
261+
endpoint = Endpoint(
262+
path="path",
263+
method="method",
264+
description=None,
265+
name="name",
266+
requires_security=False,
267+
tag="tag",
268+
relative_imports={"import_3"},
269+
)
270+
271+
endpoint._add_body({"requestBody": request_body})
272+
273+
parse_request_form_body.assert_called_once_with(request_body)
274+
parse_request_json_body.assert_called_once_with(request_body)
275+
import_string_from_reference.assert_has_calls(
276+
[mocker.call(form_body_reference, prefix="..models"), mocker.call(json_body.reference, prefix="..models")]
277+
)
278+
assert endpoint.relative_imports == {"import_1", "import_2", "import_3"}
279+
assert endpoint.json_body == json_body
280+
assert endpoint.form_body_reference == form_body_reference
281+
282+
def test__add_responses(self, mocker):
283+
from openapi_python_client.openapi_parser.openapi import Endpoint, RefResponse, Reference
284+
285+
response_1_data = mocker.MagicMock()
286+
response_2_data = mocker.MagicMock()
287+
data = {"responses": {"200": response_1_data, "404": response_2_data,}}
288+
endpoint = Endpoint(
289+
path="path",
290+
method="method",
291+
description=None,
292+
name="name",
293+
requires_security=False,
294+
tag="tag",
295+
relative_imports={"import_3"},
296+
)
297+
ref_1 = Reference(ref="ref_1")
298+
ref_2 = Reference(ref="ref_2")
299+
response_1 = RefResponse(status_code=200, content_type="application/json", reference=ref_1)
300+
response_2 = RefResponse(status_code=404, content_type="application/json", reference=ref_2)
301+
response_from_dict = mocker.patch(f"{MODULE_NAME}.response_from_dict", side_effect=[response_1, response_2])
302+
import_string_from_reference = mocker.patch(
303+
f"{MODULE_NAME}.import_string_from_reference", side_effect=["import_1", "import_2"]
304+
)
305+
306+
endpoint._add_responses(data)
307+
308+
response_from_dict.assert_has_calls(
309+
[mocker.call(status_code=200, data=response_1_data), mocker.call(status_code=404, data=response_2_data),]
310+
)
311+
import_string_from_reference.assert_has_calls(
312+
[mocker.call(ref_1, prefix="..models"), mocker.call(ref_2, prefix="..models"),]
313+
)
314+
assert endpoint.responses == [response_1, response_2]
315+
assert endpoint.relative_imports == {"import_1", "import_2", "import_3"}
316+
317+
def test__add_parameters_handles_no_params(self):
318+
from openapi_python_client.openapi_parser.openapi import Endpoint
319+
320+
endpoint = Endpoint(
321+
path="path", method="method", description=None, name="name", requires_security=False, tag="tag",
322+
)
323+
endpoint._add_parameters({}) # Just checking there's no exception here
324+
325+
def test__add_parameters_fail_loudly_when_location_not_supported(self, mocker):
326+
from openapi_python_client.openapi_parser.openapi import Endpoint
327+
328+
endpoint = Endpoint(
329+
path="path", method="method", description=None, name="name", requires_security=False, tag="tag",
330+
)
331+
mocker.patch(f"{MODULE_NAME}.property_from_dict")
332+
333+
with pytest.raises(ValueError):
334+
endpoint._add_parameters(
335+
{"parameters": [{"name": "test", "required": True, "schema": mocker.MagicMock(), "in": "cookie"}]}
336+
)
337+
338+
def test__add_parameters_happy(self, mocker):
339+
from openapi_python_client.openapi_parser.openapi import Endpoint, EnumProperty
340+
341+
endpoint = Endpoint(
342+
path="path",
343+
method="method",
344+
description=None,
345+
name="name",
346+
requires_security=False,
347+
tag="tag",
348+
relative_imports={"import_3"},
349+
)
350+
path_prop = EnumProperty(name="path_enum", required=True, default=None, values={})
351+
query_prop = EnumProperty(name="query_enum", required=False, default=None, values={})
352+
propety_from_dict = mocker.patch(f"{MODULE_NAME}.property_from_dict", side_effect=[path_prop, query_prop])
353+
path_schema = mocker.MagicMock()
354+
query_schema = mocker.MagicMock()
355+
import_string_from_reference = mocker.patch(
356+
f"{MODULE_NAME}.import_string_from_reference", side_effect=["import_1", "import_2"]
357+
)
358+
data = {
359+
"parameters": [
360+
{"name": "path_prop_name", "required": True, "schema": path_schema, "in": "path"},
361+
{"name": "query_prop_name", "required": False, "schema": query_schema, "in": "query"},
362+
]
363+
}
364+
365+
endpoint._add_parameters(data)
366+
367+
propety_from_dict.assert_has_calls(
368+
[
369+
mocker.call(name="path_prop_name", required=True, data=path_schema),
370+
mocker.call(name="query_prop_name", required=False, data=query_schema),
371+
]
372+
)
373+
import_string_from_reference.assert_has_calls(
374+
[mocker.call(path_prop.reference, prefix="..models"), mocker.call(query_prop.reference, prefix="..models"),]
375+
)
376+
assert endpoint.relative_imports == {"import_1", "import_2", "import_3"}
377+
assert endpoint.path_parameters == [path_prop]
378+
assert endpoint.query_parameters == [query_prop]
379+
380+
def test_from_data(self, mocker):
381+
from openapi_python_client.openapi_parser.openapi import Endpoint
382+
383+
path = mocker.MagicMock()
384+
method = mocker.MagicMock()
385+
_add_parameters = mocker.patch.object(Endpoint, "_add_parameters")
386+
_add_responses = mocker.patch.object(Endpoint, "_add_responses")
387+
_add_body = mocker.patch.object(Endpoint, "_add_body")
388+
data = {
389+
"description": mocker.MagicMock(),
390+
"operationId": mocker.MagicMock(),
391+
"security": {"blah": "bloo"},
392+
}
393+
394+
endpoint = Endpoint.from_data(data=data, path=path, method=method)
395+
396+
assert endpoint.path == path
397+
assert endpoint.method == method
398+
assert endpoint.description == data["description"]
399+
assert endpoint.name == data["operationId"]
400+
assert endpoint.requires_security
401+
assert endpoint.tag == "default"
402+
_add_parameters.assert_called_once_with(data)
403+
_add_responses.assert_called_once_with(data)
404+
_add_body.assert_called_once_with(data)
405+
406+
data["tags"] = ["a", "b"]
407+
del data["security"]
408+
409+
endpoint = Endpoint.from_data(data=data, path=path, method=method)
410+
411+
assert not endpoint.requires_security
412+
assert endpoint.tag == "a"
413+
229414

230415
class TestImportStringFromReference:
231416
def test_import_string_from_reference_no_prefix(self, mocker):

0 commit comments

Comments
 (0)