Skip to content

Commit ea1d6eb

Browse files
Copilotbaywet
andcommitted
test: add comprehensive tests for UnevaluatedProperties
- Add serialization tests for boolean and schema cases - Add deserialization tests for V3.1 - Add copy constructor test - Fix default value to true (allowing unevaluated properties by default) Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent 46291b9 commit ea1d6eb

File tree

4 files changed

+253
-2
lines changed

4 files changed

+253
-2
lines changed

src/Microsoft.OpenApi/Models/OpenApiSchema.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public string? Minimum
232232
public IList<JsonNode>? Enum { get; set; }
233233

234234
/// <inheritdoc />
235-
public bool UnevaluatedProperties { get; set; }
235+
public bool UnevaluatedProperties { get; set; } = true;
236236

237237
/// <inheritdoc />
238238
public IOpenApiSchema? UnevaluatedPropertiesSchema { get; set; }

src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public IList<JsonNode>? Examples
144144
/// <inheritdoc/>
145145
public IList<JsonNode>? Enum { get => Target?.Enum; }
146146
/// <inheritdoc/>
147-
public bool UnevaluatedProperties { get => Target?.UnevaluatedProperties ?? false; }
147+
public bool UnevaluatedProperties { get => Target?.UnevaluatedProperties ?? true; }
148148
/// <inheritdoc/>
149149
public IOpenApiSchema? UnevaluatedPropertiesSchema { get => (Target as IOpenApiSchemaWithUnevaluatedProperties)?.UnevaluatedPropertiesSchema; }
150150
/// <inheritdoc/>

test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,5 +707,113 @@ public void ReturnSingleIdentifierWorks()
707707
Assert.Equal("integer", type.ToSingleIdentifier());
708708
Assert.Throws<InvalidOperationException>(() => types.ToSingleIdentifier());
709709
}
710+
711+
// UnevaluatedProperties deserialization tests
712+
[Fact]
713+
public void ParseSchemaWithUnevaluatedPropertiesBooleanFalse()
714+
{
715+
// Arrange
716+
var schema = @"{
717+
""type"": ""object"",
718+
""unevaluatedProperties"": false
719+
}";
720+
721+
var expected = new OpenApiSchema()
722+
{
723+
Type = JsonSchemaType.Object,
724+
UnevaluatedProperties = false
725+
};
726+
727+
// Act
728+
var actual = OpenApiModelFactory.Parse<OpenApiSchema>(schema, OpenApiSpecVersion.OpenApi3_1, new(), out _);
729+
730+
// Assert
731+
Assert.Equivalent(expected, actual);
732+
}
733+
734+
[Fact]
735+
public void ParseSchemaWithUnevaluatedPropertiesBooleanTrue()
736+
{
737+
// Arrange - true should be parsed but is the default, effectively a no-op
738+
var schema = @"{
739+
""type"": ""object"",
740+
""unevaluatedProperties"": true
741+
}";
742+
743+
var expected = new OpenApiSchema()
744+
{
745+
Type = JsonSchemaType.Object,
746+
UnevaluatedProperties = true
747+
};
748+
749+
// Act
750+
var actual = OpenApiModelFactory.Parse<OpenApiSchema>(schema, OpenApiSpecVersion.OpenApi3_1, new(), out _);
751+
752+
// Assert
753+
Assert.Equivalent(expected, actual);
754+
}
755+
756+
[Fact]
757+
public void ParseSchemaWithUnevaluatedPropertiesSchema()
758+
{
759+
// Arrange
760+
var schema = @"{
761+
""type"": ""object"",
762+
""unevaluatedProperties"": {
763+
""type"": ""string""
764+
}
765+
}";
766+
767+
var expected = new OpenApiSchema()
768+
{
769+
Type = JsonSchemaType.Object,
770+
UnevaluatedPropertiesSchema = new OpenApiSchema
771+
{
772+
Type = JsonSchemaType.String
773+
}
774+
};
775+
776+
// Act
777+
var actual = OpenApiModelFactory.Parse<OpenApiSchema>(schema, OpenApiSpecVersion.OpenApi3_1, new(), out _);
778+
779+
// Assert
780+
Assert.Equivalent(expected, actual);
781+
}
782+
783+
[Fact]
784+
public void ParseSchemaWithUnevaluatedPropertiesComplexSchema()
785+
{
786+
// Arrange
787+
var schema = @"{
788+
""type"": ""object"",
789+
""properties"": {
790+
""name"": { ""type"": ""string"" }
791+
},
792+
""unevaluatedProperties"": {
793+
""type"": ""number"",
794+
""minimum"": ""0""
795+
}
796+
}";
797+
798+
var expected = new OpenApiSchema()
799+
{
800+
Type = JsonSchemaType.Object,
801+
Properties = new Dictionary<string, IOpenApiSchema>
802+
{
803+
["name"] = new OpenApiSchema { Type = JsonSchemaType.String }
804+
},
805+
UnevaluatedPropertiesSchema = new OpenApiSchema
806+
{
807+
Type = JsonSchemaType.Number,
808+
Minimum = "0"
809+
}
810+
};
811+
812+
// Act
813+
var actual = OpenApiModelFactory.Parse<OpenApiSchema>(schema, OpenApiSpecVersion.OpenApi3_1, new(), out _);
814+
815+
// Assert
816+
Assert.Equivalent(expected, actual);
817+
}
710818
}
711819
}

test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,38 @@ public void OpenApiSchemaCopyConstructorWithMetadataSucceeds()
486486
Assert.NotEqual(baseSchema.Metadata["key1"], actualSchema.Metadata["key1"]);
487487
}
488488

489+
[Fact]
490+
public void OpenApiSchemaCopyConstructorWithUnevaluatedPropertiesSchemaSucceeds()
491+
{
492+
var baseSchema = new OpenApiSchema
493+
{
494+
Type = JsonSchemaType.Object,
495+
UnevaluatedProperties = false,
496+
UnevaluatedPropertiesSchema = new OpenApiSchema
497+
{
498+
Type = JsonSchemaType.String,
499+
MaxLength = 100
500+
}
501+
};
502+
503+
var actualSchema = Assert.IsType<OpenApiSchema>(baseSchema.CreateShallowCopy());
504+
505+
// Verify boolean property is copied
506+
Assert.Equal(baseSchema.UnevaluatedProperties, actualSchema.UnevaluatedProperties);
507+
508+
// Verify schema property is copied
509+
Assert.NotNull(actualSchema.UnevaluatedPropertiesSchema);
510+
Assert.Equal(JsonSchemaType.String, actualSchema.UnevaluatedPropertiesSchema.Type);
511+
Assert.Equal(100, actualSchema.UnevaluatedPropertiesSchema.MaxLength);
512+
513+
// Verify it's a shallow copy (different object reference)
514+
Assert.NotSame(baseSchema.UnevaluatedPropertiesSchema, actualSchema.UnevaluatedPropertiesSchema);
515+
516+
// Verify that changing the copy doesn't affect the original
517+
actualSchema.UnevaluatedPropertiesSchema.MaxLength = 200;
518+
Assert.Equal(100, baseSchema.UnevaluatedPropertiesSchema.MaxLength);
519+
}
520+
489521
public static TheoryData<JsonNode> SchemaExamples()
490522
{
491523
return new()
@@ -1109,6 +1141,117 @@ public async Task SerializeOneOfWithNullAndRefAsV3ShouldUseNullableAsync()
11091141
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expectedV3Schema), JsonNode.Parse(v3Schema)));
11101142
}
11111143

1144+
// UnevaluatedProperties tests - similar to AdditionalProperties pattern
1145+
[Fact]
1146+
public async Task SerializeUnevaluatedPropertiesBooleanDefaultDoesNotEmit()
1147+
{
1148+
var expected = @"{ }";
1149+
// Given - default (not explicitly set) should not emit
1150+
var schema = new OpenApiSchema();
1151+
1152+
// When
1153+
var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);
1154+
1155+
// Then
1156+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1157+
}
1158+
1159+
[Fact]
1160+
public async Task SerializeUnevaluatedPropertiesBooleanTrueDoesNotEmit()
1161+
{
1162+
var expected = @"{ }";
1163+
// Given - true (allowing all) is the default, no need to emit
1164+
var schema = new OpenApiSchema
1165+
{
1166+
UnevaluatedProperties = true
1167+
};
1168+
1169+
// When
1170+
var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);
1171+
1172+
// Then
1173+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1174+
}
1175+
1176+
[Fact]
1177+
public async Task SerializeUnevaluatedPropertiesBooleanFalseEmitsInV31()
1178+
{
1179+
var expected = @"{ ""unevaluatedProperties"": false }";
1180+
// Given
1181+
var schema = new OpenApiSchema
1182+
{
1183+
UnevaluatedProperties = false
1184+
};
1185+
1186+
// When
1187+
var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);
1188+
1189+
// Then
1190+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1191+
}
1192+
1193+
[Theory]
1194+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
1195+
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
1196+
public async Task SerializeUnevaluatedPropertiesSchemaEmits(OpenApiSpecVersion version)
1197+
{
1198+
var expected = @"{ ""unevaluatedProperties"": { ""type"": ""string"" } }";
1199+
// Given
1200+
var schema = new OpenApiSchema
1201+
{
1202+
UnevaluatedPropertiesSchema = new OpenApiSchema
1203+
{
1204+
Type = JsonSchemaType.String
1205+
}
1206+
};
1207+
1208+
// When
1209+
var actual = await schema.SerializeAsJsonAsync(version);
1210+
1211+
// Then
1212+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1213+
}
1214+
1215+
[Fact]
1216+
public async Task SerializeUnevaluatedPropertiesSchemaTakesPrecedenceOverBoolean()
1217+
{
1218+
var expected = @"{ ""unevaluatedProperties"": { ""type"": ""integer"" } }";
1219+
// Given - schema should take precedence over boolean
1220+
var schema = new OpenApiSchema
1221+
{
1222+
UnevaluatedProperties = false, // This should be ignored
1223+
UnevaluatedPropertiesSchema = new OpenApiSchema
1224+
{
1225+
Type = JsonSchemaType.Integer
1226+
}
1227+
};
1228+
1229+
// When
1230+
var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);
1231+
1232+
// Then
1233+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1234+
}
1235+
1236+
[Theory]
1237+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
1238+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
1239+
public async Task SerializeUnevaluatedPropertiesNotEmittedInEarlierVersions(OpenApiSpecVersion version)
1240+
{
1241+
var expected = @"{ }";
1242+
// Given - UnevaluatedProperties should not be emitted in versions < 3.1
1243+
var schema = new OpenApiSchema
1244+
{
1245+
UnevaluatedProperties = false
1246+
};
1247+
1248+
// When
1249+
var actual = await schema.SerializeAsJsonAsync(version);
1250+
1251+
// Then
1252+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
1253+
}
1254+
11121255
internal class SchemaVisitor : OpenApiVisitorBase
11131256
{
11141257
public List<string> Titles = new();

0 commit comments

Comments
 (0)