diff --git a/src/JsonSchema.Api.Tests/JsonSchema.Api.Tests.csproj b/src/JsonSchema.Api.Tests/JsonSchema.Api.Tests.csproj index 281130903e..aaa17b2d3b 100644 --- a/src/JsonSchema.Api.Tests/JsonSchema.Api.Tests.csproj +++ b/src/JsonSchema.Api.Tests/JsonSchema.Api.Tests.csproj @@ -10,6 +10,13 @@ IDE0290 enable false + false + false + $(MSBuildProjectDirectory)\openapi + + + + true @@ -24,15 +31,25 @@ + + + + + + + diff --git a/src/JsonSchema.Api.Tests/OpenApiSchemaComparisonTests.cs b/src/JsonSchema.Api.Tests/OpenApiSchemaComparisonTests.cs new file mode 100644 index 0000000000..e7a179409b --- /dev/null +++ b/src/JsonSchema.Api.Tests/OpenApiSchemaComparisonTests.cs @@ -0,0 +1,48 @@ +#if NET9_0_OR_GREATER + +using System.Net; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using Json.Schema.Api.Tests.TestHost; +using NUnit.Framework; +using TestHelpers; + +namespace Json.Schema.Api.Tests; + +public class OpenApiSchemaComparisonTests +{ + [Test] + public async Task OpenApiSchemas_MatchGeneratedSchemas() + { + using var fixture = new ApiTestFixture(); + using var client = fixture.Client; + + var response = await client.GetAsync("/openapi/v1.json"); + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + + var openApi = JsonNode.Parse(await response.Content.ReadAsStringAsync()) as JsonObject; + Assert.That(openApi, Is.Not.Null); + + var schemas = openApi!["components"]?["schemas"] as JsonObject; + Assert.That(schemas, Is.Not.Null); + + AssertSchemaEquivalent(schemas!, nameof(SimpleModel), GeneratedJsonSchemas.SimpleModel); + AssertSchemaEquivalent(schemas!, nameof(StrictModel), GeneratedJsonSchemas.StrictModel); + AssertSchemaEquivalent(schemas!, nameof(MultiWordModel), GeneratedJsonSchemas.MultiWordModel); + } + + private static void AssertSchemaEquivalent(JsonObject openApiSchemas, string schemaName, JsonSchema generatedSchema) + { + var openApiSchema = openApiSchemas[schemaName] as JsonObject; + Assert.That(openApiSchema, Is.Not.Null, $"OpenAPI schema '{schemaName}' was not found."); + + var generated = JsonNode.Parse(generatedSchema.Root.Source.GetRawText()) as JsonObject; + Assert.That(generated, Is.Not.Null, $"Generated schema for '{schemaName}' was null."); + + generated!.Remove("$schema"); + + JsonAssert.AreEquivalent(generated, openApiSchema!); + } +} + +#endif diff --git a/src/JsonSchema.Api.Tests/TestHost/TestProgram.cs b/src/JsonSchema.Api.Tests/TestHost/TestProgram.cs index 26ed8af1aa..1e973859b2 100644 --- a/src/JsonSchema.Api.Tests/TestHost/TestProgram.cs +++ b/src/JsonSchema.Api.Tests/TestHost/TestProgram.cs @@ -3,6 +3,9 @@ using Json.Schema.Api.Tests.TestHost; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +#if NET9_0_OR_GREATER +using Microsoft.AspNetCore.OpenApi; +#endif using Microsoft.Extensions.DependencyInjection; var builder = WebApplication.CreateBuilder(new WebApplicationOptions @@ -15,9 +18,16 @@ builder.Services.AddJsonSchemaValidation(); +#if NET9_0_OR_GREATER +builder.Services.AddOpenApi(); +#endif + var app = builder.Build(); app.MapControllers(); +#if NET9_0_OR_GREATER +app.MapOpenApi(); +#endif var minimal = app.MapGroup("/minimal/test"); minimal.MapPost("/simple", (SimpleModel model) => Results.Ok(model)); diff --git a/src/JsonSchema.Api.Tests/openapi/JsonSchema.Api.Tests.json b/src/JsonSchema.Api.Tests/openapi/JsonSchema.Api.Tests.json new file mode 100644 index 0000000000..66a295bf87 --- /dev/null +++ b/src/JsonSchema.Api.Tests/openapi/JsonSchema.Api.Tests.json @@ -0,0 +1,251 @@ +{ + "openapi": "3.1.1", + "info": { + "title": "JsonSchema.Api.Tests | v1", + "version": "1.0.0" + }, + "paths": { + "/minimal/test/simple": { + "post": { + "tags": [ + "JsonSchema.Api.Tests" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/minimal/test/strict": { + "post": { + "tags": [ + "JsonSchema.Api.Tests" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StrictModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/minimal/test/multiword": { + "post": { + "tags": [ + "JsonSchema.Api.Tests" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MultiWordModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/minimal/test/unvalidated": { + "post": { + "tags": [ + "JsonSchema.Api.Tests" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnvalidatedModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/test/simple": { + "post": { + "tags": [ + "Test" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SimpleModel" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SimpleModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/test/strict": { + "post": { + "tags": [ + "Test" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StrictModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/StrictModel" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/StrictModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/test/multiword": { + "post": { + "tags": [ + "Test" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MultiWordModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/MultiWordModel" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/MultiWordModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/test/unvalidated": { + "post": { + "tags": [ + "Test" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnvalidatedModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UnvalidatedModel" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/UnvalidatedModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "MultiWordModel": { }, + "SimpleModel": { }, + "StrictModel": { }, + "UnvalidatedModel": { + "required": [ + "description" + ], + "type": "object", + "properties": { + "description": { + "type": "string" + } + } + } + } + }, + "tags": [ + { + "name": "JsonSchema.Api.Tests" + }, + { + "name": "Test" + } + ] +} \ No newline at end of file diff --git a/src/JsonSchema.Api/JsonSchema.Api.csproj b/src/JsonSchema.Api/JsonSchema.Api.csproj index aa5013a54a..57195d7dc4 100644 --- a/src/JsonSchema.Api/JsonSchema.Api.csproj +++ b/src/JsonSchema.Api/JsonSchema.Api.csproj @@ -15,8 +15,8 @@ true true snupkg - 1.1.0 - 1.1.0 + 1.0.3 + 1.0.3 1.0.0.0 Greg Dennis Extends JsonSchema.Net to provide API-centric functionality like automatic request validation