Skip to content

Commit 53fca35

Browse files
committed
misc improvements + remove redundant unit tests
1 parent b7d34a7 commit 53fca35

File tree

11 files changed

+33323
-436
lines changed

11 files changed

+33323
-436
lines changed

Diff for: end_to_end_tests/end_to_end_test_helpers.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,13 @@ def inline_spec_should_fail(
156156
) -> Result:
157157
"""Asserts that the generator could not process the spec.
158158
159-
Returns the full output.
159+
Returns the command result, which could include stdout data or an exception.
160160
"""
161161
with generate_client_from_inline_spec(
162162
openapi_spec, extra_args, filename_suffix, config, add_missing_sections, raise_on_error=False
163163
) as generated_client:
164164
assert generated_client.generator_result.exit_code != 0
165-
return generated_client.generator_result.stdout
165+
return generated_client.generator_result
166166

167167

168168
def inline_spec_should_cause_warnings(
@@ -198,6 +198,7 @@ def with_generated_client_fixture(
198198
def _decorator(cls):
199199
def generated_client(self):
200200
with generate_client_from_inline_spec(openapi_spec, extra_args=extra_args, config=config) as g:
201+
print(g.generator_result.stdout) # so we'll see the output if a test failed
201202
yield g
202203

203204
setattr(cls, name, pytest.fixture(scope="class")(generated_client))

Diff for: end_to_end_tests/generated_code_live_tests/test_defaults.py

+79-20
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
stringProp: {"type": "string", "default": "a"}
2323
numberProp: {"type": "number", "default": 1.5}
2424
intProp: {"type": "integer", "default": 2}
25-
noneProp: {"type": "null", "default": null}
25+
dateProp: {"type": "string", "format": "date", "default": "2024-01-02"}
26+
dateTimeProp: {"type": "string", "format": "date-time", "default": "2024-01-02T03:04:05Z"}
27+
uuidProp: {"type": "string", "format": "uuid", "default": "07EF8B4D-AA09-4FFA-898D-C710796AFF41"}
2628
anyPropWithString: {"default": "b"}
2729
anyPropWithInt: {"default": 3}
2830
booleanWithStringTrue1: {"type": "boolean", "default": "True"}
@@ -32,17 +34,23 @@
3234
intWithStringValue: {"type": "integer", "default": "4"}
3335
numberWithIntValue: {"type": "number", "default": 5}
3436
numberWithStringValue: {"type": "number", "default": "5.5"}
35-
noneWithStringValue: {"type": "null", "default": "None"}
37+
stringWithNumberValue: {"type": "string", "default": 6}
38+
stringConst: {"type": "string", "const": "always", "default": "always"}
3639
""")
3740
@with_generated_code_imports(".models.MyModel")
38-
class TestDefaultValues:
39-
def test_defaults_in_initializer(self, MyModel, generated_client):
41+
class TestSimpleDefaults:
42+
# Note, the null/None type is not covered here due to a known bug:
43+
# https://github.com/openapi-generators/openapi-python-client/issues/1162
44+
def test_defaults_in_initializer(self, MyModel):
4045
instance = MyModel()
4146
assert instance == MyModel(
4247
boolean_prop=True,
4348
string_prop="a",
4449
number_prop=1.5,
4550
int_prop=2,
51+
date_prop=datetime.date(2024, 1, 2),
52+
date_time_prop=datetime.datetime(2024, 1, 2, 3, 4, 5, tzinfo=datetime.timezone.utc),
53+
uuid_prop=uuid.UUID("07EF8B4D-AA09-4FFA-898D-C710796AFF41"),
4654
any_prop_with_string="b",
4755
any_prop_with_int=3,
4856
boolean_with_string_true_1=True,
@@ -52,9 +60,32 @@ def test_defaults_in_initializer(self, MyModel, generated_client):
5260
int_with_string_value=4,
5361
number_with_int_value=5,
5462
number_with_string_value=5.5,
63+
string_with_number_value="6",
64+
string_const="always",
5565
)
56-
# Note, currently the default for a None property does not work as expected--
57-
# the initializer will default it to UNSET rather than None.
66+
67+
68+
69+
@with_generated_client_fixture(
70+
"""
71+
components:
72+
schemas:
73+
MyEnum:
74+
type: string
75+
enum: ["a", "b"]
76+
MyModel:
77+
type: object
78+
properties:
79+
enumProp:
80+
allOf:
81+
- $ref: "#/components/schemas/MyEnum"
82+
default: "a"
83+
84+
""")
85+
@with_generated_code_imports(".models.MyEnum", ".models.MyModel")
86+
class TestEnumDefaults:
87+
def test_enum_default(self, MyEnum, MyModel):
88+
assert MyModel().enum_prop == MyEnum.A
5889

5990

6091
class TestInvalidDefaultValues:
@@ -79,20 +110,48 @@ def warnings(self):
79110
WithBadFloatAsOther:
80111
properties:
81112
badInt: {"type": "number", "default": true}
113+
WithBadDateAsString:
114+
properties:
115+
badDate: {"type": "string", "format": "date", "default": "xxx"}
116+
WithBadDateAsOther:
117+
properties:
118+
badDate: {"type": "string", "format": "date", "default": 3}
119+
WithBadDateTimeAsString:
120+
properties:
121+
badDate: {"type": "string", "format": "date-time", "default": "xxx"}
122+
WithBadDateTimeAsOther:
123+
properties:
124+
badDate: {"type": "string", "format": "date-time", "default": 3}
125+
WithBadUuidAsString:
126+
properties:
127+
badUuid: {"type": "string", "format": "uuid", "default": "xxx"}
128+
WithBadUuidAsOther:
129+
properties:
130+
badUuid: {"type": "string", "format": "uuid", "default": 3}
131+
WithBadEnum:
132+
properties:
133+
badEnum: {"type": "string", "enum": ["a", "b"], "default": "x"}
82134
"""
83135
)
136+
# Note, the null/None type, and binary strings (files), are not covered here due to a known bug:
137+
# https://github.com/openapi-generators/openapi-python-client/issues/1162
84138

85-
def test_bad_boolean(self, warnings):
86-
assert_bad_schema_warning(warnings, "WithBadBoolean", "Invalid boolean value")
87-
88-
def test_bad_int_as_string(self, warnings):
89-
assert_bad_schema_warning(warnings, "WithBadIntAsString", "Invalid int value")
90-
91-
def test_bad_int_as_other(self, warnings):
92-
assert_bad_schema_warning(warnings, "WithBadIntAsOther", "Invalid int value")
93-
94-
def test_bad_float_as_string(self, warnings):
95-
assert_bad_schema_warning(warnings, "WithBadFloatAsString", "Invalid float value")
96-
97-
def test_bad_float_as_other(self, warnings):
98-
assert_bad_schema_warning(warnings, "WithBadFloatAsOther", "Cannot convert True to a float")
139+
@pytest.mark.parametrize(
140+
("model_name", "message"),
141+
[
142+
("WithBadBoolean", "Invalid boolean value"),
143+
("WithBadIntAsString", "Invalid int value"),
144+
("WithBadIntAsOther", "Invalid int value"),
145+
("WithBadFloatAsString", "Invalid float value"),
146+
("WithBadFloatAsOther", "Cannot convert True to a float"),
147+
("WithBadDateAsString", "Invalid date"),
148+
("WithBadDateAsOther", "Cannot convert 3 to a date"),
149+
("WithBadDateTimeAsString", "Invalid datetime"),
150+
("WithBadDateTimeAsOther", "Cannot convert 3 to a datetime"),
151+
("WithBadUuidAsString", "Invalid UUID value"),
152+
("WithBadUuidAsOther", "Invalid UUID value"),
153+
("WithBadEnum", "Value x is not valid for enum"),
154+
]
155+
)
156+
def test_bad_default_warning(self, model_name, message, warnings):
157+
assert_bad_schema_warning(warnings, model_name, message)

Diff for: end_to_end_tests/generated_code_live_tests/test_enum_and_const.py

+51-71
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
assert_bad_schema_warning,
66
assert_model_decode_encode,
77
inline_spec_should_cause_warnings,
8+
inline_spec_should_fail,
89
with_generated_code_import,
910
with_generated_client_fixture,
1011
with_generated_code_imports,
@@ -17,10 +18,15 @@
1718
schemas:
1819
MyEnum:
1920
type: string
20-
enum: ["a", "B"]
21+
enum: ["a", "B", "~weirdstring"]
2122
MyIntEnum:
2223
type: integer
23-
enum: [2, 3]
24+
enum: [2, 3, -4]
25+
MyEnumIncludingNull:
26+
type: ["string", "null"]
27+
enum: ["a", "b", null]
28+
MyNullOnlyEnum:
29+
enum: [null]
2430
MyModel:
2531
properties:
2632
enumProp: {"$ref": "#/components/schemas/MyEnum"}
@@ -29,34 +35,53 @@
2935
oneOf:
3036
- {"$ref": "#/components/schemas/MyEnum"}
3137
- type: "null"
38+
enumIncludingNullProp: {"$ref": "#/components/schemas/MyEnumIncludingNull"}
39+
nullOnlyEnumProp: {"$ref": "#/components/schemas/MyNullOnlyEnum"}
40+
inlineEnumProp:
41+
type: string
42+
enum: ["a", "b"]
3243
""")
33-
@with_generated_code_imports(".models.MyEnum", ".models.MyIntEnum", ".models.MyModel")
44+
@with_generated_code_imports(
45+
".models.MyEnum",
46+
".models.MyIntEnum",
47+
".models.MyEnumIncludingNullType1", # see comment in test_nullable_enum_prop
48+
".models.MyModel",
49+
".models.MyModelInlineEnumProp",
50+
)
3451
class TestEnumClasses:
3552
def test_enum_classes(self, MyEnum, MyIntEnum):
3653
assert MyEnum.A == MyEnum("a")
3754
assert MyEnum.B == MyEnum("B")
55+
assert MyEnum.VALUE_2 == MyEnum("~weirdstring")
3856
assert MyIntEnum.VALUE_2 == MyIntEnum(2)
3957
assert MyIntEnum.VALUE_3 == MyIntEnum(3)
58+
assert MyIntEnum.VALUE_NEGATIVE_4 == MyIntEnum(-4)
4059

41-
def test_enum_prop(self, MyModel, MyEnum, MyIntEnum):
60+
def test_enum_prop(self, MyModel, MyEnum, MyIntEnum, MyModelInlineEnumProp):
4261
assert_model_decode_encode(MyModel, {"enumProp": "B"}, MyModel(enum_prop=MyEnum.B))
4362
assert_model_decode_encode(MyModel, {"intEnumProp": 2}, MyModel(int_enum_prop=MyIntEnum.VALUE_2))
44-
45-
def test_enum_prop_type(self, MyModel, MyEnum, MyIntEnum):
46-
assert isinstance(MyModel.from_dict({"enumProp": "B"}).enum_prop, MyEnum)
47-
assert isinstance(MyModel.from_dict({"intEnumProp": 2}).int_enum_prop, MyIntEnum)
48-
49-
def test_nullable_enum_prop(self, MyModel, MyEnum):
5063
assert_model_decode_encode(
5164
MyModel,
52-
{"nullableEnumProp": "B"},
53-
MyModel(nullable_enum_prop=MyEnum.B),
65+
{"inlineEnumProp": "a"},
66+
MyModel(inline_enum_prop=MyModelInlineEnumProp.A),
5467
)
68+
69+
def test_enum_prop_type(self, MyModel, MyEnum):
70+
# Just verifying that it's not using a literal vaue
71+
assert isinstance(MyModel.from_dict({"enumProp": "B"}).enum_prop, MyEnum)
72+
73+
def test_nullable_enum_prop(self, MyModel, MyEnum, MyEnumIncludingNullType1):
74+
# Note, MyEnumIncludingNullType1 should be named just MyEnumIncludingNull -
75+
# known bug: https://github.com/openapi-generators/openapi-python-client/issues/1120
76+
assert_model_decode_encode(MyModel, {"nullableEnumProp": "B"}, MyModel(nullable_enum_prop=MyEnum.B))
77+
assert_model_decode_encode(MyModel, {"nullableEnumProp": None}, MyModel(nullable_enum_prop=None))
5578
assert_model_decode_encode(
5679
MyModel,
57-
{"nullableEnumProp": None},
58-
MyModel(nullable_enum_prop=None),
80+
{"enumIncludingNullProp": "a"},
81+
MyModel(enum_including_null_prop=MyEnumIncludingNullType1.A),
5982
)
83+
assert_model_decode_encode( MyModel, {"enumIncludingNullProp": None}, MyModel(enum_including_null_prop=None))
84+
assert_model_decode_encode(MyModel, {"nullOnlyEnumProp": None}, MyModel(null_only_enum_prop=None))
6085

6186
def test_invalid_values(self, MyModel):
6287
with pytest.raises(ValueError):
@@ -71,63 +96,6 @@ def test_invalid_values(self, MyModel):
7196
MyModel.from_dict({"intEnumProp": "a"})
7297

7398

74-
@with_generated_client_fixture(
75-
"""
76-
components:
77-
schemas:
78-
MyEnum:
79-
type: string
80-
enum: ["a", "A"]
81-
MyIntEnum:
82-
type: integer
83-
enum: [2, 3]
84-
MyModel:
85-
properties:
86-
enumProp: {"$ref": "#/components/schemas/MyEnum"}
87-
intEnumProp: {"$ref": "#/components/schemas/MyIntEnum"}
88-
nullableEnumProp:
89-
oneOf:
90-
- {"$ref": "#/components/schemas/MyEnum"}
91-
- type: "null"
92-
""",
93-
config="""
94-
literal_enums: true
95-
""",
96-
)
97-
@with_generated_code_import(".models.MyModel")
98-
class TestLiteralEnums:
99-
def test_enum_prop(self, MyModel):
100-
assert_model_decode_encode(MyModel, {"enumProp": "a"}, MyModel(enum_prop="a"))
101-
assert_model_decode_encode(MyModel, {"enumProp": "A"}, MyModel(enum_prop="A"))
102-
assert_model_decode_encode(MyModel, {"intEnumProp": 2}, MyModel(int_enum_prop=2))
103-
104-
def test_enum_prop_type(self, MyModel):
105-
assert MyModel.from_dict({"enumProp": "a"}).enum_prop.__class__ is str
106-
assert MyModel.from_dict({"intEnumProp": 2}).int_enum_prop.__class__ is int
107-
108-
def test_nullable_enum_prop(self, MyModel):
109-
assert_model_decode_encode(
110-
MyModel,
111-
{"nullableEnumProp": "a"},
112-
MyModel(nullable_enum_prop="a"),
113-
)
114-
assert_model_decode_encode(
115-
MyModel,
116-
{"nullableEnumProp": None},
117-
MyModel(nullable_enum_prop=None),
118-
)
119-
120-
def test_invalid_values(self, MyModel):
121-
with pytest.raises(TypeError):
122-
MyModel.from_dict({"enumProp": "c"})
123-
with pytest.raises(TypeError):
124-
MyModel.from_dict({"enumProp": 2})
125-
with pytest.raises(TypeError):
126-
MyModel.from_dict({"intEnumProp": 0})
127-
with pytest.raises(TypeError):
128-
MyModel.from_dict({"intEnumProp": "a"})
129-
130-
13199
@with_generated_client_fixture(
132100
"""
133101
components:
@@ -202,3 +170,15 @@ def test_enum_unsupported_type(self, warnings):
202170

203171
def test_const_default_not_matching(self, warnings):
204172
assert_bad_schema_warning(warnings, "DefaultNotMatchingConst", "Invalid value for const")
173+
174+
def test_enum_duplicate_values(self):
175+
# This one currently causes a full generator failure rather than a warning
176+
result = inline_spec_should_fail(
177+
"""
178+
components:
179+
schemas:
180+
WithDuplicateValues:
181+
enum: ["x", "x"]
182+
"""
183+
)
184+
assert "Duplicate key X in enum" in str(result.exception)

0 commit comments

Comments
 (0)