Skip to content

Commit 7fe1e80

Browse files
committed
Add test coverage for allOf with nullable
1 parent 71366d4 commit 7fe1e80

8 files changed

+1654
-0
lines changed

src/OpenApi/sample/Endpoints/MapSchemasEndpoints.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,26 @@ public static IEndpointRouteBuilder MapSchemasEndpoints(this IEndpointRouteBuild
4646
schemas.MapPost("/config-with-generic-lists", (Config config) => Results.Ok(config));
4747
schemas.MapPost("/project-response", (ProjectResponse project) => Results.Ok(project));
4848
schemas.MapPost("/subscription", (Subscription subscription) => Results.Ok(subscription));
49+
50+
// Tests for allOf nullable behavior on responses and request bodies
51+
schemas.MapGet("/nullable-response", () => TypedResults.Ok(new NullableResponseModel
52+
{
53+
RequiredProperty = "required",
54+
NullableProperty = null,
55+
NullableComplexProperty = null
56+
}));
57+
schemas.MapPost("/nullable-request", (NullableRequestModel? request) => Results.Ok(request));
58+
schemas.MapPost("/complex-nullable-hierarchy", (ComplexHierarchyModel model) => Results.Ok(model));
59+
60+
// Additional edge cases for nullable testing
61+
schemas.MapPost("/nullable-array-elements", (NullableArrayModel model) => Results.Ok(model));
62+
schemas.MapGet("/optional-with-default", () => Results.Ok(new ModelWithDefaults()));
63+
schemas.MapGet("/nullable-enum-response", () => Results.Ok(new EnumNullableModel
64+
{
65+
RequiredEnum = TestEnum.Value1,
66+
NullableEnum = null
67+
}));
68+
4969
return endpointRouteBuilder;
5070
}
5171

@@ -173,4 +193,73 @@ public sealed class RefUser
173193
public string Name { get; set; } = "";
174194
public string Email { get; set; } = "";
175195
}
196+
197+
// Models for testing allOf nullable behavior
198+
public sealed class NullableResponseModel
199+
{
200+
public required string RequiredProperty { get; set; }
201+
public string? NullableProperty { get; set; }
202+
public ComplexType? NullableComplexProperty { get; set; }
203+
}
204+
205+
public sealed class NullableRequestModel
206+
{
207+
public required string RequiredField { get; set; }
208+
public string? OptionalField { get; set; }
209+
public List<string>? NullableList { get; set; }
210+
public Dictionary<string, string?>? NullableDictionary { get; set; }
211+
}
212+
213+
// Complex hierarchy model for testing nested nullable properties
214+
public sealed class ComplexHierarchyModel
215+
{
216+
public required string Id { get; set; }
217+
public NestedModel? OptionalNested { get; set; }
218+
public required NestedModel RequiredNested { get; set; }
219+
public List<NestedModel?>? NullableListWithNullableItems { get; set; }
220+
}
221+
222+
public sealed class NestedModel
223+
{
224+
public required string Name { get; set; }
225+
public int? OptionalValue { get; set; }
226+
public ComplexType? DeepNested { get; set; }
227+
}
228+
229+
public sealed class ComplexType
230+
{
231+
public string? Description { get; set; }
232+
public DateTime? Timestamp { get; set; }
233+
}
234+
235+
// Additional models for edge case testing
236+
public sealed class NullableArrayModel
237+
{
238+
public string[]? NullableArray { get; set; }
239+
public List<string?> ListWithNullableElements { get; set; } = [];
240+
public Dictionary<string, string?>? NullableDictionaryWithNullableValues { get; set; }
241+
}
242+
243+
public sealed class ModelWithDefaults
244+
{
245+
public string PropertyWithDefault { get; set; } = "default";
246+
public string? NullableWithNull { get; set; }
247+
public int NumberWithDefault { get; set; } = 42;
248+
public bool BoolWithDefault { get; set; } = true;
249+
}
250+
251+
// Enum testing with nullable
252+
public enum TestEnum
253+
{
254+
Value1,
255+
Value2,
256+
Value3
257+
}
258+
259+
public sealed class EnumNullableModel
260+
{
261+
public required TestEnum RequiredEnum { get; set; }
262+
public TestEnum? NullableEnum { get; set; }
263+
public List<TestEnum?> ListOfNullableEnums { get; set; } = [];
264+
}
176265
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApi3_0/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=schemas-by-ref.verified.txt

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,121 @@
636636
}
637637
}
638638
}
639+
},
640+
"/schemas-by-ref/nullable-response": {
641+
"get": {
642+
"tags": [
643+
"Sample"
644+
],
645+
"responses": {
646+
"200": {
647+
"description": "OK",
648+
"content": {
649+
"application/json": {
650+
"schema": {
651+
"$ref": "#/components/schemas/NullableResponseModel"
652+
}
653+
}
654+
}
655+
}
656+
}
657+
}
658+
},
659+
"/schemas-by-ref/nullable-request": {
660+
"post": {
661+
"tags": [
662+
"Sample"
663+
],
664+
"requestBody": {
665+
"content": {
666+
"application/json": {
667+
"schema": {
668+
"allOf": [
669+
{
670+
"nullable": true
671+
},
672+
{
673+
"$ref": "#/components/schemas/NullableRequestModel"
674+
}
675+
]
676+
}
677+
}
678+
}
679+
},
680+
"responses": {
681+
"200": {
682+
"description": "OK"
683+
}
684+
}
685+
}
686+
},
687+
"/schemas-by-ref/complex-nullable-hierarchy": {
688+
"post": {
689+
"tags": [
690+
"Sample"
691+
],
692+
"requestBody": {
693+
"content": {
694+
"application/json": {
695+
"schema": {
696+
"$ref": "#/components/schemas/ComplexHierarchyModel"
697+
}
698+
}
699+
},
700+
"required": true
701+
},
702+
"responses": {
703+
"200": {
704+
"description": "OK"
705+
}
706+
}
707+
}
708+
},
709+
"/schemas-by-ref/nullable-array-elements": {
710+
"post": {
711+
"tags": [
712+
"Sample"
713+
],
714+
"requestBody": {
715+
"content": {
716+
"application/json": {
717+
"schema": {
718+
"$ref": "#/components/schemas/NullableArrayModel"
719+
}
720+
}
721+
},
722+
"required": true
723+
},
724+
"responses": {
725+
"200": {
726+
"description": "OK"
727+
}
728+
}
729+
}
730+
},
731+
"/schemas-by-ref/optional-with-default": {
732+
"get": {
733+
"tags": [
734+
"Sample"
735+
],
736+
"responses": {
737+
"200": {
738+
"description": "OK"
739+
}
740+
}
741+
}
742+
},
743+
"/schemas-by-ref/nullable-enum-response": {
744+
"get": {
745+
"tags": [
746+
"Sample"
747+
],
748+
"responses": {
749+
"200": {
750+
"description": "OK"
751+
}
752+
}
753+
}
639754
}
640755
},
641756
"components": {
@@ -707,6 +822,52 @@
707822
}
708823
}
709824
},
825+
"ComplexHierarchyModel": {
826+
"required": [
827+
"id",
828+
"requiredNested"
829+
],
830+
"type": "object",
831+
"properties": {
832+
"id": {
833+
"type": "string"
834+
},
835+
"optionalNested": {
836+
"allOf": [
837+
{
838+
"nullable": true
839+
},
840+
{
841+
"$ref": "#/components/schemas/NestedModel"
842+
}
843+
]
844+
},
845+
"requiredNested": {
846+
"$ref": "#/components/schemas/NestedModel"
847+
},
848+
"nullableListWithNullableItems": {
849+
"type": "array",
850+
"items": {
851+
"$ref": "#/components/schemas/NestedModel"
852+
},
853+
"nullable": true
854+
}
855+
}
856+
},
857+
"ComplexType": {
858+
"type": "object",
859+
"properties": {
860+
"description": {
861+
"type": "string",
862+
"nullable": true
863+
},
864+
"timestamp": {
865+
"type": "string",
866+
"format": "date-time",
867+
"nullable": true
868+
}
869+
}
870+
},
710871
"Config": {
711872
"type": "object",
712873
"properties": {
@@ -898,6 +1059,111 @@
8981059
}
8991060
}
9001061
},
1062+
"NestedModel": {
1063+
"required": [
1064+
"name"
1065+
],
1066+
"type": "object",
1067+
"properties": {
1068+
"name": {
1069+
"type": "string"
1070+
},
1071+
"optionalValue": {
1072+
"type": "integer",
1073+
"format": "int32",
1074+
"nullable": true
1075+
},
1076+
"deepNested": {
1077+
"allOf": [
1078+
{
1079+
"nullable": true
1080+
},
1081+
{
1082+
"$ref": "#/components/schemas/ComplexType"
1083+
}
1084+
]
1085+
}
1086+
}
1087+
},
1088+
"NullableArrayModel": {
1089+
"type": "object",
1090+
"properties": {
1091+
"nullableArray": {
1092+
"type": "array",
1093+
"items": {
1094+
"type": "string"
1095+
},
1096+
"nullable": true
1097+
},
1098+
"listWithNullableElements": {
1099+
"type": "array",
1100+
"items": {
1101+
"type": "string"
1102+
}
1103+
},
1104+
"nullableDictionaryWithNullableValues": {
1105+
"type": "object",
1106+
"additionalProperties": {
1107+
"type": "string"
1108+
},
1109+
"nullable": true
1110+
}
1111+
}
1112+
},
1113+
"NullableRequestModel": {
1114+
"required": [
1115+
"requiredField"
1116+
],
1117+
"type": "object",
1118+
"properties": {
1119+
"requiredField": {
1120+
"type": "string"
1121+
},
1122+
"optionalField": {
1123+
"type": "string",
1124+
"nullable": true
1125+
},
1126+
"nullableList": {
1127+
"type": "array",
1128+
"items": {
1129+
"type": "string"
1130+
},
1131+
"nullable": true
1132+
},
1133+
"nullableDictionary": {
1134+
"type": "object",
1135+
"additionalProperties": {
1136+
"type": "string"
1137+
},
1138+
"nullable": true
1139+
}
1140+
}
1141+
},
1142+
"NullableResponseModel": {
1143+
"required": [
1144+
"requiredProperty"
1145+
],
1146+
"type": "object",
1147+
"properties": {
1148+
"requiredProperty": {
1149+
"type": "string"
1150+
},
1151+
"nullableProperty": {
1152+
"type": "string",
1153+
"nullable": true
1154+
},
1155+
"nullableComplexProperty": {
1156+
"allOf": [
1157+
{
1158+
"nullable": true
1159+
},
1160+
{
1161+
"$ref": "#/components/schemas/ComplexType"
1162+
}
1163+
]
1164+
}
1165+
}
1166+
},
9011167
"ParentObject": {
9021168
"type": "object",
9031169
"properties": {

0 commit comments

Comments
 (0)