Skip to content

Commit 85e758d

Browse files
committed
test(schema): add V32 dynamic-ref mirror and breadth/ambiguous coverage
Mirror the V31 dynamic-ref tests under V32Tests so the duplicated V32 deserializer block and SerializeAsV32 path are exercised (previously only V31 was tested). Add V31 coverage for: - ambiguous anchors (same name in multiple subschemas) -> Target null - $dynamicAnchor registered across every subschema location the walk descends into (oneOf/anyOf/not/items/additionalProperties/ patternProperties/contains/propertyNames/contentSchema/if-then-else/ dependentSchemas/unevaluatedPropertiesSchema) - $dynamicAnchor inside a $ref schema's applicator siblings (oneOf/ anyOf/properties/items/patternProperties/dependentSchemas), exercising the JsonSchemaReference child enumerator This brings the PR's added/changed executable code to full coverage; remaining Cobertura hits are method-signature declaration lines and one pre-existing unreachable defensive throw.
1 parent 6fb3610 commit 85e758d

2 files changed

Lines changed: 446 additions & 0 deletions

File tree

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

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.IO;
2+
using System.Linq;
23
using System.Text;
34
using System.Text.Json.Nodes;
45
using System.Threading.Tasks;
@@ -199,6 +200,228 @@ public async Task DynamicRefResolvesViaAllOfAnchor()
199200
Assert.Same(branch, reference.Target);
200201
}
201202

203+
[Fact]
204+
public async Task DynamicRefReturnsNullWhenAnchorIsAmbiguous()
205+
{
206+
// When a single document declares the same $dynamicAnchor name on more than one subschema,
207+
// resolution cannot pick one without dynamic-scope evaluation, so Target returns null.
208+
var yaml =
209+
"""
210+
openapi: 3.1.0
211+
info:
212+
title: Test
213+
version: 1.0.0
214+
paths: {}
215+
components:
216+
schemas:
217+
Root:
218+
type: object
219+
properties:
220+
a:
221+
$dynamicAnchor: node
222+
type: object
223+
b:
224+
$dynamicAnchor: node
225+
type: object
226+
ref:
227+
$dynamicRef: '#node'
228+
""";
229+
230+
var doc = await LoadDocumentAsync(yaml);
231+
232+
var root = doc.Components.Schemas["Root"];
233+
var reference = Assert.IsType<OpenApiSchemaReference>(root.Properties["ref"]);
234+
Assert.Null(reference.Target);
235+
}
236+
237+
[Fact]
238+
public async Task DynamicAnchorRegisteredAcrossAllSubschemaLocations()
239+
{
240+
// Exercises every subschema location the anchor walk descends into (oneOf, anyOf, not,
241+
// items, additionalProperties, patternProperties, contains, propertyNames, contentSchema,
242+
// if/then/else, dependentSchemas, unevaluatedPropertiesSchema). Each declares a distinct
243+
// $dynamicAnchor name; a $dynamicRef to each confirms the walk reached it.
244+
var yaml =
245+
"""
246+
openapi: 3.1.0
247+
info:
248+
title: Test
249+
version: 1.0.0
250+
paths: {}
251+
components:
252+
schemas:
253+
Root:
254+
type: object
255+
oneOf:
256+
- $dynamicAnchor: one
257+
type: object
258+
anyOf:
259+
- $dynamicAnchor: any
260+
type: object
261+
allOf:
262+
- type: object
263+
properties:
264+
nested:
265+
type: array
266+
items:
267+
$dynamicAnchor: itm
268+
type: string
269+
dependentSchemas:
270+
dep:
271+
$dynamicAnchor: depn
272+
type: object
273+
not:
274+
$dynamicAnchor: notn
275+
type: object
276+
contains:
277+
$dynamicAnchor: cont
278+
type: object
279+
propertyNames:
280+
$dynamicAnchor: pn
281+
type: string
282+
contentSchema:
283+
$dynamicAnchor: cs
284+
type: string
285+
unevaluatedProperties:
286+
$dynamicAnchor: up
287+
type: object
288+
patternProperties:
289+
'^x':
290+
$dynamicAnchor: pp
291+
type: object
292+
properties:
293+
child:
294+
$dynamicAnchor: ifn
295+
type: object
296+
additionalProperties:
297+
$dynamicAnchor: ap
298+
type: object
299+
if:
300+
$dynamicAnchor: iftop
301+
type: object
302+
then:
303+
$dynamicAnchor: thentop
304+
type: object
305+
else:
306+
$dynamicAnchor: elsetop
307+
type: object
308+
$defs:
309+
consumer:
310+
type: object
311+
properties:
312+
one:
313+
$dynamicRef: '#one'
314+
any:
315+
$dynamicRef: '#any'
316+
notn:
317+
$dynamicRef: '#notn'
318+
cont:
319+
$dynamicRef: '#cont'
320+
pn:
321+
$dynamicRef: '#pn'
322+
cs:
323+
$dynamicRef: '#cs'
324+
up:
325+
$dynamicRef: '#up'
326+
pp:
327+
$dynamicRef: '#pp'
328+
ifn:
329+
$dynamicRef: '#ifn'
330+
itm:
331+
$dynamicRef: '#itm'
332+
depn:
333+
$dynamicRef: '#depn'
334+
iftop:
335+
$dynamicRef: '#iftop'
336+
thentop:
337+
$dynamicRef: '#thentop'
338+
elsetop:
339+
$dynamicRef: '#elsetop'
340+
ap:
341+
$dynamicRef: '#ap'
342+
""";
343+
344+
var doc = await LoadDocumentAsync(yaml);
345+
var root = doc.Components.Schemas["Root"];
346+
var consumer = root.Definitions["consumer"].Properties;
347+
348+
// Each anchor is unique within the document, so every resolution must succeed.
349+
foreach (var name in new[] { "one", "any", "notn", "cont", "pn", "cs", "up", "pp", "ifn", "itm", "depn", "iftop", "thentop", "elsetop", "ap" })
350+
{
351+
var reference = Assert.IsType<OpenApiSchemaReference>(consumer[name]);
352+
Assert.NotNull(reference.Target);
353+
}
354+
}
355+
356+
[Fact]
357+
public async Task DynamicAnchorInRefSiblingApplicatorsIsRegistered()
358+
{
359+
// A $ref schema may carry applicator siblings (allOf/oneOf/anyOf/properties/items/...) whose
360+
// subschemas declare $dynamicAnchor. The anchor walk must descend into a reference holder's
361+
// own siblings (read from JsonSchemaReference) to register them.
362+
var yaml =
363+
"""
364+
openapi: 3.1.0
365+
info:
366+
title: Test
367+
version: 1.0.0
368+
paths: {}
369+
components:
370+
schemas:
371+
Base:
372+
type: object
373+
Referencing:
374+
$ref: '#/components/schemas/Base'
375+
oneOf:
376+
- $dynamicAnchor: refOne
377+
type: object
378+
anyOf:
379+
- $dynamicAnchor: refAny
380+
type: object
381+
properties:
382+
child:
383+
$dynamicAnchor: refChild
384+
type: object
385+
patternProperties:
386+
'^x':
387+
$dynamicAnchor: refPP
388+
type: object
389+
items:
390+
$dynamicAnchor: refItem
391+
type: string
392+
dependentSchemas:
393+
dep:
394+
$dynamicAnchor: refDep
395+
type: object
396+
$defs:
397+
consumer:
398+
type: object
399+
properties:
400+
a:
401+
$dynamicRef: '#refOne'
402+
b:
403+
$dynamicRef: '#refAny'
404+
c:
405+
$dynamicRef: '#refChild'
406+
d:
407+
$dynamicRef: '#refItem'
408+
e:
409+
$dynamicRef: '#refPP'
410+
f:
411+
$dynamicRef: '#refDep'
412+
""";
413+
414+
var doc = await LoadDocumentAsync(yaml);
415+
var referencing = doc.Components.Schemas["Referencing"];
416+
var consumer = referencing.Definitions["consumer"].Properties;
417+
418+
// Each $dynamicRef targets an anchor declared in a sibling applicator of the $ref schema.
419+
foreach (var reference in consumer.Values.Select(v => Assert.IsType<OpenApiSchemaReference>(v)))
420+
{
421+
Assert.NotNull(reference.Target);
422+
}
423+
}
424+
202425
[Fact]
203426
public async Task DynamicRefReturnsNullForUnknownAnchor()
204427
{

0 commit comments

Comments
 (0)