@@ -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