File: Services\CreateSchemaReferenceIdTests.cs
Web Access
Project: src\src\OpenApi\test\Microsoft.AspNetCore.OpenApi.Tests\Microsoft.AspNetCore.OpenApi.Tests.csproj (Microsoft.AspNetCore.OpenApi.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text.Json.Serialization.Metadata;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
 
public class CreateSchemaReferenceIdTests : OpenApiDocumentServiceTestBase
{
    [Fact]
    public async Task HandlesPolymorphicTypeWithCustomReferenceIds()
    {
        // Arrange
        var builder = CreateBuilder();
 
        // Act
        builder.MapPost("/api", (Shape shape) => { });
        string createReferenceId(JsonTypeInfo jsonTypeInfo)
        {
            return jsonTypeInfo.Type.Name switch
            {
                "Shape" => "MyShape",
                "Triangle" => "MyTriangle",
                "Square" => "MySquare",
                _ => jsonTypeInfo.Type.Name,
            };
        }
        var options = new OpenApiOptions { CreateSchemaReferenceId = createReferenceId };
 
        // Assert
        await VerifyOpenApiDocument(builder, options, document =>
        {
            var operation = document.Paths["/api"].Operations[OperationType.Post];
            Assert.NotNull(operation.RequestBody);
            var requestBody = operation.RequestBody.Content;
            Assert.True(requestBody.TryGetValue("application/json", out var mediaType));
            var schema = mediaType.Schema.GetEffective(document);
            // Assert discriminator mappings have been configured correctly
            Assert.Equal("$type", schema.Discriminator.PropertyName);
            Assert.Contains(schema.Discriminator.PropertyName, schema.Required);
            Assert.Collection(schema.Discriminator.Mapping,
                item => Assert.Equal("triangle", item.Key),
                item => Assert.Equal("square", item.Key)
            );
            Assert.Collection(schema.Discriminator.Mapping,
                item => Assert.Equal("#/components/schemas/MyShapeMyTriangle", item.Value),
                item => Assert.Equal("#/components/schemas/MyShapeMySquare", item.Value)
            );
            // Assert the schemas with the discriminator have been inserted into the components
            Assert.True(document.Components.Schemas.TryGetValue("MyShapeMyTriangle", out var triangleSchema));
            Assert.Contains(schema.Discriminator.PropertyName, triangleSchema.Properties.Keys);
            Assert.Equal("triangle", ((OpenApiString)triangleSchema.Properties[schema.Discriminator.PropertyName].Enum.First()).Value);
            Assert.True(document.Components.Schemas.TryGetValue("MyShapeMySquare", out var squareSchema));
            Assert.Equal("square", ((OpenApiString)squareSchema.Properties[schema.Discriminator.PropertyName].Enum.First()).Value);
        });
    }
 
    [Fact]
    public async Task GeneratesSchemaForPoco_WithSchemaReferenceIdCustomization()
    {
        // Arrange
        var builder = CreateBuilder();
 
        // Act
        builder.MapPost("/", (Todo todo) => { });
        var options = new OpenApiOptions { CreateSchemaReferenceId = (type) => type.Type.Name == "Todo" ? $"{type.Type.Name}Schema" : OpenApiOptions.CreateDefaultSchemaReferenceId(type) };
 
        // Assert
        await VerifyOpenApiDocument(builder, options, document =>
        {
            var operation = document.Paths["/"].Operations[OperationType.Post];
            var requestBody = operation.RequestBody;
 
            Assert.NotNull(requestBody);
            var content = Assert.Single(requestBody.Content);
            Assert.Equal("application/json", content.Key);
            Assert.NotNull(content.Value.Schema);
            Assert.Equal("TodoSchema", content.Value.Schema.Reference.Id);
            var schema = content.Value.Schema.GetEffective(document);
            Assert.Equal("object", schema.Type);
            Assert.Collection(schema.Properties,
                property =>
                {
                    Assert.Equal("id", property.Key);
                    Assert.Equal("integer", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("title", property.Key);
                    Assert.Equal("string", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("completed", property.Key);
                    Assert.Equal("boolean", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("createdAt", property.Key);
                    Assert.Equal("string", property.Value.Type);
                    Assert.Equal("date-time", property.Value.Format);
                });
 
        });
    }
 
    [Fact]
    public async Task GeneratesInlineSchemaForPoco_WithCustomNullId()
    {
        // Arrange
        var builder = CreateBuilder();
 
        // Act
        builder.MapPost("/", (Todo todo) => { });
        var options = new OpenApiOptions { CreateSchemaReferenceId = (type) => type.Type.Name == "Todo" ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type) };
 
        // Assert
        await VerifyOpenApiDocument(builder, options, document =>
        {
            var operation = document.Paths["/"].Operations[OperationType.Post];
            var requestBody = operation.RequestBody;
 
            Assert.NotNull(requestBody);
            var content = Assert.Single(requestBody.Content);
            Assert.Equal("application/json", content.Key);
            Assert.NotNull(content.Value.Schema);
            // Assert that no reference was created and the schema is inlined
            var schema = content.Value.Schema;
            Assert.Null(schema.Reference);
            Assert.Equal("object", schema.Type);
            Assert.Collection(schema.Properties,
                property =>
                {
                    Assert.Equal("id", property.Key);
                    Assert.Equal("integer", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("title", property.Key);
                    Assert.Equal("string", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("completed", property.Key);
                    Assert.Equal("boolean", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("createdAt", property.Key);
                    Assert.Equal("string", property.Value.Type);
                    Assert.Equal("date-time", property.Value.Format);
                });
 
        });
    }
 
    [Fact]
    public async Task CanCallDefaultImplementationFromCustomOne()
    {
        var builder = CreateBuilder();
 
        builder.MapPost("/", (Todo todo) => new TodoWithDueDate(todo.Id, todo.Title, todo.Completed, todo.CreatedAt, DateTime.UtcNow));
        var options = new OpenApiOptions
        {
            CreateSchemaReferenceId = (type) =>
            {
                if (type.Type.Name == "Todo")
                {
                    return null;
                }
                return OpenApiOptions.CreateDefaultSchemaReferenceId(type);
            }
        };
 
        await VerifyOpenApiDocument(builder, options, document =>
        {
            var operation = document.Paths["/"].Operations[OperationType.Post];
            var requestBody = operation.RequestBody;
            var response = operation.Responses["200"];
 
            // Assert that no reference was created for the Todo type
            Assert.NotNull(requestBody);
            var content = Assert.Single(requestBody.Content);
            Assert.Equal("application/json", content.Key);
            Assert.NotNull(content.Value.Schema);
            var schema = content.Value.Schema;
            Assert.Null(schema.Reference);
 
            // Assert that a reference was created for the TodoWithDueDate type
            Assert.NotNull(response);
            var responseContent = Assert.Single(response.Content);
            Assert.Equal("application/json", responseContent.Key);
            Assert.NotNull(responseContent.Value.Schema);
            var responseSchema = responseContent.Value.Schema;
            Assert.NotNull(responseSchema.Reference);
            Assert.Equal("TodoWithDueDate", responseSchema.Reference.Id);
        });
    }
 
    [Fact]
    public async Task HandlesDuplicateSchemaReferenceIdsGeneratedByOverload()
    {
        var builder = CreateBuilder();
 
        builder.MapPost("/", (Todo todo) => new TodoWithDueDate(todo.Id, todo.Title, todo.Completed, todo.CreatedAt, DateTime.UtcNow));
        var options = new OpenApiOptions
        {
            CreateSchemaReferenceId = (type) =>
            {
                if (type.Type.Name == "TodoWithDueDate" || type.Type.Name == "Todo")
                {
                    return "Todo";
                }
                return OpenApiOptions.CreateDefaultSchemaReferenceId(type);
            }
        };
 
        await VerifyOpenApiDocument(builder, options, document =>
        {
            var operation = document.Paths["/"].Operations[OperationType.Post];
            var requestBody = operation.RequestBody;
            var response = operation.Responses["200"];
 
            // Assert that a reference was created for the Todo type
            Assert.NotNull(requestBody);
            var content = Assert.Single(requestBody.Content);
            Assert.Equal("application/json", content.Key);
            Assert.NotNull(content.Value.Schema);
            var schema = content.Value.Schema;
            Assert.NotNull(schema.Reference);
 
            // Assert that a reference was created for the TodoWithDueDate type
            Assert.NotNull(response);
            var responseContent = Assert.Single(response.Content);
            Assert.Equal("application/json", responseContent.Key);
            Assert.NotNull(responseContent.Value.Schema);
            var responseSchema = responseContent.Value.Schema;
            Assert.NotNull(responseSchema.Reference);
 
            // Assert that the reference IDs are not the same (have been deduped)
            Assert.NotEqual(schema.Reference.Id, responseSchema.Reference.Id);
 
            // Assert that the referenced schemas are correct
            var effectiveResponseSchema = responseSchema.GetEffective(document);
            Assert.Equal("object", effectiveResponseSchema.Type);
            Assert.Collection(effectiveResponseSchema.Properties,
                property =>
                {
                    Assert.Equal("dueDate", property.Key);
                    Assert.Equal("string", property.Value.Type);
                    Assert.Equal("date-time", property.Value.Format);
                },
                property =>
                {
                    Assert.Equal("id", property.Key);
                    Assert.Equal("integer", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("title", property.Key);
                    Assert.Equal("string", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("completed", property.Key);
                    Assert.Equal("boolean", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("createdAt", property.Key);
                    Assert.Equal("string", property.Value.Type);
                    Assert.Equal("date-time", property.Value.Format);
                });
 
            var effectiveRequestSchema = schema.GetEffective(document);
            Assert.Equal("object", effectiveRequestSchema.Type);
            Assert.Collection(effectiveRequestSchema.Properties,
                property =>
                {
                    Assert.Equal("id", property.Key);
                    Assert.Equal("integer", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("title", property.Key);
                    Assert.Equal("string", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("completed", property.Key);
                    Assert.Equal("boolean", property.Value.Type);
                },
                property =>
                {
                    Assert.Equal("createdAt", property.Key);
                    Assert.Equal("string", property.Value.Type);
                    Assert.Equal("date-time", property.Value.Format);
                });
        });
    }
 
}