Skip to content

Commit f91ec37

Browse files
committed
test: achieve 100% test coverage codebase wide
1 parent 4b354f6 commit f91ec37

7 files changed

Lines changed: 162 additions & 51 deletions

File tree

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,11 @@ celerybeat.pid
151151
# Pyenv / Misc
152152
.python-version
153153
*.snapshot.json
154+
155+
# Profiling and Patching
156+
*.patch
157+
*.diff
158+
*.pyprof
159+
*.lprof
160+
*.pstats
161+
*.cprof

src/openapi_client/functions/emit.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ def emit_function(method: str, path: str, operation: Operation) -> cst.FunctionD
3636
has_query = False
3737
has_body_param = False
3838
body_param_name = "body"
39+
has_form_data = False
40+
form_data_params = []
3941

4042
# Process query, header, path, cookie parameters
4143
if operation.parameters:
@@ -86,6 +88,10 @@ def emit_function(method: str, path: str, operation: Operation) -> cst.FunctionD
8688

8789
# Process query params
8890
p_in = getattr(param, "in_", getattr(param, "in", None))
91+
if p_in in ("formData", "form"):
92+
has_form_data = True
93+
form_data_params.append((param.name, param_name))
94+
8995
if p_in == "query":
9096
style = getattr(param, "style", "form")
9197
delim = (
@@ -203,11 +209,14 @@ def emit_function(method: str, path: str, operation: Operation) -> cst.FunctionD
203209
)
204210
body_statements.extend(query_statements)
205211

206-
url_value = cst.BinaryOperation(
207-
left=cst.Attribute(value=cst.Name("self"), attr=cst.Name("base_url")),
208-
operator=cst.Add(),
209-
right=cst.SimpleString(f'"{path}"'),
210-
)
212+
if "{" in path and "}" in path:
213+
url_value = cst.parse_expression(f'self.base_url + f"{path}"')
214+
else:
215+
url_value = cst.BinaryOperation(
216+
left=cst.Attribute(value=cst.Name("self"), attr=cst.Name("base_url")),
217+
operator=cst.Add(),
218+
right=cst.SimpleString(f'"{path}"'),
219+
)
211220
if has_query:
212221
url_value = cst.BinaryOperation(
213222
left=url_value,
@@ -281,7 +290,23 @@ def emit_function(method: str, path: str, operation: Operation) -> cst.FunctionD
281290
)
282291
]
283292
if operation.requestBody or has_body_param
284-
else []
293+
else (
294+
[
295+
cst.Arg(
296+
keyword=cst.Name("fields"),
297+
value=cst.Dict(
298+
elements=[
299+
cst.DictElement(
300+
key=cst.SimpleString(f'"{k}"'),
301+
value=cst.Name(v)
302+
)
303+
for k, v in form_data_params
304+
]
305+
)
306+
)
307+
]
308+
if has_form_data else []
309+
)
285310
),
286311
),
287312
)

src/openapi_client/tests/emit.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,21 @@ def get_dummy_value_for_schema(schema_obj) -> cst.BaseExpression:
3131
elif ann.startswith("dict"):
3232
return cst.Dict(elements=[])
3333
else:
34+
# Fallback to a string instead of None so urllib3 doesn't crash on encode_multipart_formdata
35+
t = getattr(schema_obj, "type", None) if schema_obj else None
36+
if t == "file":
37+
return cst.SimpleString('b"dummy_content"')
3438
return cst.Name("None")
3539

3640

37-
def get_stub_body(schema_obj):
41+
def get_stub_body(schema_obj, spec=None):
3842
"""
3943
Generate a stub body AST node based on an OpenAPI schema object.
4044
"""
45+
if schema_obj and getattr(schema_obj, "ref", None) and spec and spec.components and spec.components.schemas:
46+
ref_name = schema_obj.ref.split('/')[-1]
47+
schema_obj = spec.components.schemas.get(ref_name, schema_obj)
48+
4149
if schema_obj and getattr(schema_obj, "properties", None):
4250
elements = []
4351
for prop_name, prop_schema in schema_obj.properties.items():
@@ -48,7 +56,10 @@ def get_stub_body(schema_obj):
4856
elements=[cst.Element(cst.SimpleString('"http://dummy"'))]
4957
)
5058
else:
51-
val = get_dummy_value_for_schema(prop_schema)
59+
if getattr(prop_schema, "ref", None) or getattr(prop_schema, "type", None) in ("object", "array"):
60+
val = get_stub_body(prop_schema, spec)
61+
else:
62+
val = get_dummy_value_for_schema(prop_schema)
5263
elements.append(
5364
cst.DictElement(
5465
key=cst.SimpleString(f'"{prop_name}"'), value=val
@@ -60,7 +71,7 @@ def get_stub_body(schema_obj):
6071
if items_schema and getattr(items_schema, "type", None) in ("string", "integer", "number", "boolean"):
6172
item_val = get_dummy_value_for_schema(items_schema)
6273
else:
63-
item_val = get_stub_body(items_schema)
74+
item_val = get_stub_body(items_schema, spec)
6475
return cst.List(
6576
elements=[
6677
cst.Element(item_val)
@@ -70,7 +81,7 @@ def get_stub_body(schema_obj):
7081
return cst.Dict(elements=[])
7182

7283
def emit_operation_test(
73-
method: str, path: str, operation: Operation, composable: bool = False
84+
method: str, path: str, operation: Operation, composable: bool = False, spec=None
7485
) -> cst.FunctionDef:
7586
"""
7687
Emit a pytest unit test for an API operation.
@@ -96,11 +107,13 @@ def emit_operation_test(
96107
schema_obj = getattr(
97108
param, "schema_", getattr(param, "schema", None)
98109
)
99-
val = get_stub_body(schema_obj)
110+
val = get_stub_body(schema_obj, spec)
100111
else:
101112
schema_obj = getattr(
102113
param, "schema_", getattr(param, "schema", None)
103114
)
115+
if not schema_obj and getattr(param, "type", None):
116+
schema_obj = param
104117
val = get_dummy_value_for_schema(schema_obj)
105118
args.append(cst.Arg(keyword=cst.Name(param_name), value=val))
106119

@@ -129,7 +142,7 @@ def emit_operation_test(
129142
getattr(content[first_key], "schema", None),
130143
)
131144

132-
val = get_stub_body(schema_obj)
145+
val = get_stub_body(schema_obj, spec)
133146
except Exception:
134147
val = cst.Dict(elements=[])
135148

@@ -463,7 +476,7 @@ def emit_tests(spec: OpenAPI, composable: bool = False) -> cst.Module:
463476
if operation:
464477
body.append(
465478
emit_operation_test(
466-
method, path, operation, composable=composable
479+
method, path, operation, composable=composable, spec=spec
467480
)
468481
)
469482
body.append(cst.EmptyLine())

test/test_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66

77
@pytest.fixture
88
def client():
9-
return Client(os.getenv("API_URL", "http://localhost:8080/v2"))
9+
return Client(os.getenv("API_URL", "http://localhost:8080/v2"), api_key = "special-key")
1010

tests/test_compliance_updates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ def test_emit_tests_exception():
205205

206206
# Bypass pydantic validation to force an exception in the try block
207207
class Bad:
208-
def __bool__(self):
209-
return True
208+
def __contains__(self, item):
209+
raise Exception("Mock error")
210210

211211
rb = RequestBody.model_construct(content=Bad())
212212
op = Operation(operationId="err", requestBody=rb)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import pytest
2+
from openapi_client.models import Operation, Parameter, RequestBody, MediaType, Schema
3+
from openapi_client.functions.emit import emit_function
4+
5+
def test_emit_function_formData_and_no_path_vars():
6+
# Test path without "{}"
7+
path = "/no/vars/here"
8+
method = "post"
9+
10+
# Test param with in="formData"
11+
param = Parameter(
12+
name="my_form_param",
13+
in_="formData",
14+
required=True,
15+
schema=Schema(type="string")
16+
)
17+
18+
op = Operation(
19+
operationId="test_formData_op",
20+
parameters=[param]
21+
)
22+
23+
func_node = emit_function(method, path, op)
24+
assert func_node is not None
25+
assert func_node.name.value == "test_formData_op"
26+
27+
from openapi_client.tests.emit import get_dummy_value_for_schema, get_stub_body, emit_operation_test
28+
from openapi_client.models import Schema, OpenAPI, Components, Reference
29+
30+
def test_get_dummy_value_for_file():
31+
schema = Schema(type="file")
32+
val = get_dummy_value_for_schema(schema)
33+
assert val.value == 'b"dummy_content"'
34+
35+
def test_get_stub_body_ref():
36+
ref_schema = Reference(ref="#/components/schemas/Target")
37+
target_schema = Schema(type="object", properties={"some_prop": Schema(type="string")})
38+
spec = OpenAPI(
39+
openapi="3.2.0",
40+
info={"title": "test", "version": "1.0"},
41+
paths={},
42+
components=Components(schemas={"Target": target_schema})
43+
)
44+
val = get_stub_body(ref_schema, spec)
45+
assert val is not None
46+
47+
def test_get_stub_body_prop_ref():
48+
ref_schema = Reference(ref="#/components/schemas/Target")
49+
target_schema = Schema(type="object", properties={"some_prop": Schema(type="string")})
50+
main_schema = Schema(
51+
type="object",
52+
properties={
53+
"my_ref": ref_schema,
54+
"my_obj": Schema(type="object", properties={"inner": Schema(type="string")})
55+
}
56+
)
57+
spec = OpenAPI(
58+
openapi="3.2.0",
59+
info={"title": "test", "version": "1.0"},
60+
paths={},
61+
components=Components(schemas={"Target": target_schema})
62+
)
63+
val = get_stub_body(main_schema, spec)
64+
assert val is not None
65+
66+
def test_emit_operation_test_param_no_schema():
67+
param = Parameter(name="p", in_="query", type="string")
68+
op = Operation(operationId="test_param_type", parameters=[param])
69+
node = emit_operation_test("get", "/test", op)
70+
assert node is not None
71+
72+
def test_emit_function_with_path_vars():
73+
path = "/users/{id}"
74+
method = "get"
75+
76+
op = Operation(
77+
operationId="get_user",
78+
parameters=[]
79+
)
80+
81+
func_node = emit_function(method, path, op)
82+
assert func_node is not None
83+
assert func_node.name.value == "get_user"

tests/test_coverage_emit_test.py

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ def test_emit_tests_exception_content():
5757
from openapi_client.tests.emit import emit_operation_test
5858
from openapi_client.models import Operation, RequestBody
5959

60-
class BadDict(dict):
61-
def keys(self):
60+
class BadDict:
61+
def __contains__(self, item):
6262
raise Exception("Mock error")
6363

64-
op = Operation(
64+
op = Operation.model_construct(
6565
operationId="test_exception",
66-
requestBody=RequestBody(content=BadDict())
66+
requestBody=RequestBody.model_construct(content=BadDict())
6767
)
6868
func = emit_operation_test("post", "/test", op)
6969
assert func.name.value == "test_test_exception"
@@ -73,17 +73,16 @@ def test_emit_tests_body_param_exception():
7373
from openapi_client.tests.emit import emit_operation_test
7474
from openapi_client.models import Operation, Parameter
7575

76-
class BadParameter(Parameter):
76+
class BadParameter:
77+
name = 'body_param'
78+
in_ = 'body'
7779
@property
7880
def schema_(self):
7981
raise Exception("Mock error")
80-
@property
81-
def schema(self):
82-
raise Exception("Mock error")
8382

84-
op = Operation(
83+
op = Operation.model_construct(
8584
operationId="test_body_param_exception",
86-
parameters=[BadParameter(name="body_param", in_="body")]
85+
parameters=[BadParameter()]
8786
)
8887
try:
8988
func = emit_operation_test("post", "/test", op)
@@ -106,53 +105,36 @@ def test_emit_tests_request_body_exception():
106105
from openapi_client.tests.emit import emit_operation_test
107106
from openapi_client.models import Operation, RequestBody
108107

109-
class BadRequestBody(RequestBody):
108+
class BadRequestBody:
109+
pass
110110
@property
111111
def content(self):
112112
raise Exception("Mock error")
113113

114-
op = Operation(
114+
op = Operation.model_construct(
115115
operationId="test_request_body_exception",
116116
requestBody=BadRequestBody()
117117
)
118118
func = emit_operation_test("post", "/test", op)
119119
assert func.name.value == "test_test_request_body_exception"
120120

121121

122-
def test_emit_tests_body_param_exception_schema():
123-
from openapi_client.tests.emit import emit_operation_test
124-
from openapi_client.models import Operation, Parameter
125-
126-
class BadParameter2(Parameter):
127-
@property
128-
def schema(self):
129-
raise Exception("Mock error")
130-
131-
op = Operation(
132-
operationId="test_body_param_exception_2",
133-
parameters=[BadParameter2(name="body_param_2", in_="body")]
134-
)
135-
try:
136-
func = emit_operation_test("post", "/test", op)
137-
except Exception:
138-
pass
139122

140123
def test_emit_tests_other_param_exception():
141124
from openapi_client.tests.emit import emit_operation_test
142125
from openapi_client.models import Operation, Parameter
143126

144-
class BadParameter3(Parameter):
145-
@property
146-
def schema(self):
147-
raise Exception("Mock error")
127+
class BadParameter3:
128+
name = 'other_param_3'
129+
in_ = 'query'
148130

149131
@property
150132
def schema_(self):
151133
raise Exception("Mock error")
152134

153-
op = Operation(
135+
op = Operation.model_construct(
154136
operationId="test_other_param_exception",
155-
parameters=[BadParameter3(name="other_param_3", in_="query")]
137+
parameters=[BadParameter3()]
156138
)
157139
try:
158140
func = emit_operation_test("post", "/test", op)

0 commit comments

Comments
 (0)