Skip to content

Commit fcf4dbf

Browse files
committed
test: add empty-collection fallthrough, 3.0 version safety, and $vocabulary round-trip tests
- Empty $defs: {} / $vocabulary: {} must fall through to Target (guards the .Count > 0 fix in commit 4666f2c) - 3.0 document with $ref + siblings must drop siblings per spec (guards the version-safety guarantee) - $vocabulary round-trip (parse -> serialize -> parse) Ref: #2895
1 parent 4666f2c commit fcf4dbf

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

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

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,5 +1188,122 @@ public async Task ParseSchemaReferencePreservesDynamicAnchorInsideDefsInAllOf()
11881188
contentType.Should().BeOfType<OpenApiSchemaReference>();
11891189
contentType.DynamicAnchor.Should().Be("contentType");
11901190
}
1191+
1192+
[Fact]
1193+
public async Task EmptySiblingCollectionsFallThroughToTarget()
1194+
{
1195+
// Arrange — Target has $defs and $vocabulary; Referencing has empty siblings.
1196+
// Empty collections must NOT suppress the target's values via the ?? getter.
1197+
var yaml = """
1198+
openapi: 3.1.0
1199+
info:
1200+
title: Empty sibling fallthrough
1201+
version: 1.0.0
1202+
paths: {}
1203+
components:
1204+
schemas:
1205+
Target:
1206+
type: object
1207+
$defs:
1208+
targetDef:
1209+
type: string
1210+
$vocabulary:
1211+
'https://json-schema.org/draft/2020-12/vocab/core': true
1212+
Referencing:
1213+
$ref: '#/components/schemas/Target'
1214+
$defs: {}
1215+
$vocabulary: {}
1216+
""";
1217+
1218+
// Act
1219+
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml));
1220+
var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings);
1221+
var referencing = result.Document.Components!.Schemas["Referencing"];
1222+
1223+
// Assert — empty siblings fall through to Target's values
1224+
referencing.Should().BeOfType<OpenApiSchemaReference>();
1225+
referencing.Definitions.Should().NotBeNull();
1226+
referencing.Definitions!.Should().ContainKey("targetDef");
1227+
referencing.Vocabulary.Should().NotBeNull();
1228+
referencing.Vocabulary!.Should().ContainKey("https://json-schema.org/draft/2020-12/vocab/core");
1229+
}
1230+
1231+
[Fact]
1232+
public async Task SiblingsOnRefAreDroppedForOpenApi30()
1233+
{
1234+
// Arrange — 3.0 spec requires $ref siblings to be ignored.
1235+
// The fix must not change 3.0 behavior.
1236+
var yaml = """
1237+
openapi: 3.0.3
1238+
info:
1239+
title: 3.0 version safety
1240+
version: 1.0.0
1241+
paths: {}
1242+
components:
1243+
schemas:
1244+
Target:
1245+
type: object
1246+
properties:
1247+
name:
1248+
type: string
1249+
Referencing:
1250+
$ref: '#/components/schemas/Target'
1251+
$dynamicAnchor: anchor
1252+
$defs:
1253+
sibling:
1254+
$dynamicAnchor: inner
1255+
$ref: '#/components/schemas/Target'
1256+
""";
1257+
1258+
// Act
1259+
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml));
1260+
var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings);
1261+
var referencing = result.Document.Components!.Schemas["Referencing"];
1262+
1263+
// Assert — siblings are dropped for 3.0 (per spec: $ref siblings MUST be ignored)
1264+
referencing.Should().BeOfType<OpenApiSchemaReference>();
1265+
referencing.DynamicAnchor.Should().BeNull();
1266+
referencing.Definitions?.Should().BeNull();
1267+
}
1268+
1269+
[Fact]
1270+
public async Task SerializeSchemaReferencePreservesVocabularySibling()
1271+
{
1272+
// Arrange
1273+
var yaml = """
1274+
openapi: 3.1.0
1275+
info:
1276+
title: Vocabulary round-trip
1277+
version: 1.0.0
1278+
paths: {}
1279+
components:
1280+
schemas:
1281+
Target:
1282+
type: object
1283+
Referencing:
1284+
$ref: '#/components/schemas/Target'
1285+
$vocabulary:
1286+
'https://json-schema.org/draft/2020-12/vocab/core': true
1287+
'https://json-schema.org/draft/2020-12/vocab/applicator': false
1288+
""";
1289+
1290+
// Act — parse then serialize back
1291+
using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml));
1292+
var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings);
1293+
var writer = new StringWriter();
1294+
result.Document.SerializeAsV31(new OpenApiYamlWriter(writer));
1295+
var output = writer.ToString();
1296+
1297+
// Assert — round-trip preserves $vocabulary alongside $ref
1298+
using var roundTripStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(output));
1299+
var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings);
1300+
var referencing = roundTripResult.Document.Components!.Schemas["Referencing"];
1301+
1302+
referencing.Should().BeOfType<OpenApiSchemaReference>();
1303+
referencing.Vocabulary.Should().NotBeNull();
1304+
referencing.Vocabulary!.Should().HaveCount(2);
1305+
referencing.Vocabulary["https://json-schema.org/draft/2020-12/vocab/core"].Should().BeTrue();
1306+
referencing.Vocabulary["https://json-schema.org/draft/2020-12/vocab/applicator"].Should().BeFalse();
1307+
}
11911308
}
11921309
}

0 commit comments

Comments
 (0)