Skip to content

Commit 502f8c7

Browse files
[OpenApi] Test schema reference validity
Add integration test for invalid OpenAPI schema references. Relates to #63090.
1 parent 87cd4b4 commit 502f8c7

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/OpenApiDocumentIntegrationTests.cs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text.Json.Nodes;
45
using Microsoft.AspNetCore.InternalTesting;
56
using Microsoft.AspNetCore.OpenApi;
67
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.OpenApi.Reader;
79

810
[UsesVerify]
911
public sealed class OpenApiDocumentIntegrationTests(SampleAppFixture fixture) : IClassFixture<SampleAppFixture>
@@ -66,6 +68,152 @@ public async Task OpenApiDocumentIsValid(string documentName, OpenApiSpecVersion
6668
Assert.Empty(errors);
6769
}
6870

71+
[Theory] // See https://github.com/dotnet/aspnetcore/issues/63090
72+
[MemberData(nameof(OpenApiDocuments))]
73+
public async Task OpenApiDocumentReferencesAreValid(string documentName, OpenApiSpecVersion version)
74+
{
75+
var json = await GetOpenApiDocument(documentName, version);
76+
77+
var result = OpenApiDocument.Parse(json, format: "json");
78+
79+
var document = result.Document;
80+
var documentNode = JsonNode.Parse(json);
81+
82+
var actual = new List<string>();
83+
84+
// TODO What other parts of the document should also be validated for references to be comprehensive?
85+
// Likely also needs to be recursive to validate all references in schemas, parameters, etc.
86+
if (document.Components is { Schemas.Count: > 0 } components)
87+
{
88+
foreach (var schema in components.Schemas)
89+
{
90+
if (schema.Value.Properties is { Count: > 0 } properties)
91+
{
92+
foreach (var property in properties)
93+
{
94+
if (property.Value is not OpenApiSchemaReference reference)
95+
{
96+
continue;
97+
}
98+
99+
var id = reference.Reference.ReferenceV3;
100+
101+
if (!IsValidSchemaReference(id, documentNode))
102+
{
103+
actual.Add($"Reference '{id}' on property '{property.Key}' of schema '{schema.Key}' is invalid.");
104+
}
105+
}
106+
}
107+
108+
if (schema.Value.AllOf is { Count: > 0 } allOf)
109+
{
110+
foreach (var child in allOf)
111+
{
112+
if (child is not OpenApiSchemaReference reference)
113+
{
114+
continue;
115+
}
116+
117+
var id = reference.Reference.ReferenceV3;
118+
119+
if (!IsValidSchemaReference(id, documentNode))
120+
{
121+
actual.Add($"Reference '{id}' for AllOf of schema '{schema.Key}' is invalid.");
122+
}
123+
}
124+
}
125+
126+
if (schema.Value.AnyOf is { Count: > 0 } anyOf)
127+
{
128+
foreach (var child in anyOf)
129+
{
130+
if (child is not OpenApiSchemaReference reference)
131+
{
132+
continue;
133+
}
134+
135+
var id = reference.Reference.ReferenceV3;
136+
137+
if (!IsValidSchemaReference(id, documentNode))
138+
{
139+
actual.Add($"Reference '{id}' for AnyOf of schema '{schema.Key}' is invalid.");
140+
}
141+
}
142+
}
143+
144+
if (schema.Value.OneOf is { Count: > 0 } oneOf)
145+
{
146+
foreach (var child in oneOf)
147+
{
148+
if (child is not OpenApiSchemaReference reference)
149+
{
150+
continue;
151+
}
152+
153+
var id = reference.Reference.ReferenceV3;
154+
155+
if (!IsValidSchemaReference(id, documentNode))
156+
{
157+
actual.Add($"Reference '{id}' for OneOf of schema '{schema.Key}' is invalid.");
158+
}
159+
}
160+
}
161+
162+
if (schema.Value.Discriminator is { Mapping.Count: > 0 } discriminator)
163+
{
164+
foreach (var child in discriminator.Mapping)
165+
{
166+
if (child.Value is not OpenApiSchemaReference reference)
167+
{
168+
continue;
169+
}
170+
171+
var id = reference.Reference.ReferenceV3;
172+
173+
if (!IsValidSchemaReference(id, documentNode))
174+
{
175+
actual.Add($"Reference '{id}' for Discriminator '{child.Key}' of schema '{schema.Key}' is invalid.");
176+
}
177+
}
178+
}
179+
}
180+
}
181+
182+
foreach (var path in document.Paths)
183+
{
184+
foreach (var operation in path.Value.Operations)
185+
{
186+
if (operation.Value.Parameters is not { Count: > 0 } parameters)
187+
{
188+
continue;
189+
}
190+
191+
foreach (var parameter in parameters)
192+
{
193+
if (parameter.Schema is not OpenApiSchemaReference reference)
194+
{
195+
continue;
196+
}
197+
198+
var id = reference.Reference.ReferenceV3;
199+
200+
if (!IsValidSchemaReference(id, documentNode))
201+
{
202+
actual.Add($"Reference '{id}' on parameter '{parameter.Name}' of path '{path.Key}' of operation '{operation.Key}' is invalid.");
203+
}
204+
}
205+
}
206+
}
207+
208+
Assert.Empty(actual);
209+
210+
static bool IsValidSchemaReference(string id, JsonNode baseNode)
211+
{
212+
var pointer = new JsonPointer(id.Replace("#/", "/"));
213+
return pointer.Find(baseNode) is not null;
214+
}
215+
}
216+
69217
private async Task<string> GetOpenApiDocument(string documentName, OpenApiSpecVersion version)
70218
{
71219
var documentService = fixture.Services.GetRequiredKeyedService<OpenApiDocumentService>(documentName);

0 commit comments

Comments
 (0)