// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ using System; using System.Linq; using Microsoft.OData.Edm; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Generator; using Xunit; using Xunit.Abstractions; namespace Microsoft.OpenApi.OData.Tests { public class OpenApiSchemaGeneratorTest { private ITestOutputHelper _output; public OpenApiSchemaGeneratorTest(ITestOutputHelper output) { _output = output; } [Fact] public void CreateSchemasThrowArgumentNullContext() { // Arrange ODataContext context = null; // Act & Assert Assert.Throws("context", () => context.CreateSchemas()); } #region StructuredTypeSchema [Fact] public void CreateStructuredTypeSchemaThrowArgumentNullContext() { // Arrange ODataContext context = null; // Act & Assert Assert.Throws("context", () => context.CreateStructuredTypeSchema(structuredType: null)); } [Fact] public void CreateStructuredTypeSchemaThrowArgumentNullEnumType() { // Arrange ODataContext context = new ODataContext(EdmCoreModel.Instance); // Act & Assert Assert.Throws("structuredType", () => context.CreateStructuredTypeSchema(structuredType: null)); } [Fact] public void CreateComplexTypeWithoutBaseSchemaReturnCorrectSchema() { // Arrange IEdmModel model = EdmModelHelper.MultipleInheritanceEdmModel; ODataContext context = new ODataContext(model); IEdmComplexType complex = model.SchemaElements.OfType().First(t => t.Name == "Address"); Assert.NotNull(complex); // Guard // Act var schema = context.CreateStructuredTypeSchema(complex); // Assert Assert.NotNull(schema); Assert.Equal("object", schema.Type); Assert.Null(schema.AllOf); Assert.NotNull(schema.Properties); Assert.Equal(2, schema.Properties.Count); Assert.Equal(new string[] { "Street", "City" }, schema.Properties.Select(e => e.Key)); Assert.Equal("Complex type 'Address' description.", schema.Description); Assert.Equal("Address", schema.Title); // Act string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert Assert.NotNull(json); Assert.Equal(@"{ ""title"": ""Address"", ""type"": ""object"", ""properties"": { ""Street"": { ""type"": ""string"", ""nullable"": true }, ""City"": { ""type"": ""string"", ""nullable"": true } }, ""description"": ""Complex type 'Address' description."" }" .ChangeLineBreaks(), json); } [Fact] public void CreateComplexTypeWithBaseSchemaReturnCorrectSchema() { // Arrange IEdmModel model = EdmModelHelper.MultipleInheritanceEdmModel; ODataContext context = new ODataContext(model, new OpenApiConvertSettings { IEEE754Compatible = true }); IEdmComplexType complex = model.SchemaElements.OfType().First(t => t.Name == "Tree"); Assert.NotNull(complex); // Guard // Act var schema = context.CreateStructuredTypeSchema(complex); // Assert Assert.NotNull(schema); Assert.True(String.IsNullOrEmpty(schema.Type)); Assert.NotNull(schema.AllOf); Assert.Null(schema.AnyOf); Assert.Null(schema.OneOf); Assert.Null(schema.Properties); Assert.Equal(2, schema.AllOf.Count); var baseSchema = schema.AllOf.First(); Assert.NotNull(baseSchema.Reference); Assert.Equal(ReferenceType.Schema, baseSchema.Reference.Type); Assert.Equal("NS.LandPlant", baseSchema.Reference.Id); var declaredSchema = schema.AllOf.Last(); Assert.Equal("object", declaredSchema.Type); Assert.Null(declaredSchema.AllOf); Assert.Null(declaredSchema.AnyOf); Assert.Null(declaredSchema.OneOf); Assert.NotNull(declaredSchema.Properties); Assert.Equal(1, declaredSchema.Properties.Count); var property = Assert.Single(declaredSchema.Properties); Assert.Equal("Price", property.Key); Assert.Equal("decimal", property.Value.Format); Assert.NotNull(property.Value.AnyOf); Assert.Equal(new string[] { "number", "string" }, property.Value.AnyOf.Select(e => e.Type)); Assert.Equal("Complex type 'Tree' description.", declaredSchema.Description); Assert.Equal("Tree", declaredSchema.Title); // Act string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert Assert.NotNull(json); Assert.Equal(@"{ ""allOf"": [ { ""$ref"": ""#/components/schemas/NS.LandPlant"" }, { ""title"": ""Tree"", ""type"": ""object"", ""properties"": { ""Price"": { ""multipleOf"": 1, ""anyOf"": [ { ""type"": ""number"" }, { ""type"": ""string"" } ], ""format"": ""decimal"" } }, ""description"": ""Complex type 'Tree' description."" } ] }" .ChangeLineBreaks(), json); } [Fact] public void CreateEntityTypeWithoutBaseSchemaReturnCorrectSchema() { // Arrange IEdmModel model = EdmModelHelper.MultipleInheritanceEdmModel; ODataContext context = new ODataContext(model); IEdmEntityType entity = model.SchemaElements.OfType().First(t => t.Name == "Zoo"); Assert.NotNull(entity); // Guard // Act var schema = context.CreateStructuredTypeSchema(entity); // Assert Assert.NotNull(schema); Assert.Equal("object", schema.Type); Assert.Null(schema.AllOf); Assert.NotNull(schema.Properties); Assert.Equal(2, schema.Properties.Count); Assert.Equal(new string[] { "Id", "Creatures" }, schema.Properties.Select(e => e.Key)); Assert.Equal("Entity type 'Zoo' description.", schema.Description); Assert.Equal("Zoo", schema.Title); // Act string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert Assert.NotNull(json); Assert.Equal(@"{ ""title"": ""Zoo"", ""type"": ""object"", ""properties"": { ""Id"": { ""maximum"": 2147483647, ""minimum"": -2147483648, ""type"": ""integer"", ""format"": ""int32"" }, ""Creatures"": { ""type"": ""array"", ""items"": { ""$ref"": ""#/components/schemas/NS.Creature"" } } }, ""description"": ""Entity type 'Zoo' description."" }" .ChangeLineBreaks(), json); } [Fact] public void CreateEntityTypeWithBaseSchemaReturnCorrectSchema() { // Arrange IEdmModel model = EdmModelHelper.MultipleInheritanceEdmModel; ODataContext context = new ODataContext(model); IEdmEntityType entity = model.SchemaElements.OfType().First(t => t.Name == "Human"); Assert.NotNull(entity); // Guard // Act var schema = context.CreateStructuredTypeSchema(entity); // Assert Assert.NotNull(schema); Assert.True(String.IsNullOrEmpty(schema.Type)); Assert.NotNull(schema.AllOf); Assert.Null(schema.AnyOf); Assert.Null(schema.OneOf); Assert.Null(schema.Properties); Assert.Equal(2, schema.AllOf.Count); var baseSchema = schema.AllOf.First(); Assert.NotNull(baseSchema.Reference); Assert.Equal(ReferenceType.Schema, baseSchema.Reference.Type); Assert.Equal("NS.Animal", baseSchema.Reference.Id); var declaredSchema = schema.AllOf.Last(); Assert.Equal("object", declaredSchema.Type); Assert.Null(declaredSchema.AllOf); Assert.Null(declaredSchema.AnyOf); Assert.Null(declaredSchema.OneOf); Assert.NotNull(declaredSchema.Properties); Assert.Equal(1, declaredSchema.Properties.Count); var property = Assert.Single(declaredSchema.Properties); Assert.Equal("Name", property.Key); Assert.Equal("string", property.Value.Type); Assert.Null(property.Value.OneOf); Assert.Equal("Entity type 'Human' description.", declaredSchema.Description); Assert.Equal("Human", declaredSchema.Title); // Act string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); _output.WriteLine(json); // Assert Assert.NotNull(json); Assert.Equal(@"{ ""allOf"": [ { ""$ref"": ""#/components/schemas/NS.Animal"" }, { ""title"": ""Human"", ""type"": ""object"", ""properties"": { ""Name"": { ""type"": ""string"" } }, ""description"": ""Entity type 'Human' description."" } ] }" .ChangeLineBreaks(), json); } #endregion #region EnumTypeSchema [Fact] public void CreateEnumTypeSchemaThrowArgumentNullContext() { // Arrange ODataContext context = null; // Act & Assert Assert.Throws("context", () => context.CreateEnumTypeSchema(enumType: null)); } [Fact] public void CreateEnumTypeSchemaThrowArgumentNullEnumType() { // Arrange ODataContext context = new ODataContext(EdmCoreModel.Instance); // Act & Assert Assert.Throws("enumType", () => context.CreateEnumTypeSchema(enumType: null)); } [Fact] public void CreateEnumTypeSchemaReturnCorrectSchema() { // Arrange IEdmModel model = EdmModelHelper.BasicEdmModel; ODataContext context = new ODataContext(model); IEdmEnumType enumType = model.SchemaElements.OfType().First(t => t.Name == "Color"); Assert.NotNull(enumType); // Guard // Act var schema = context.CreateEnumTypeSchema(enumType); // Assert Assert.NotNull(schema); Assert.Equal("string", schema.Type); Assert.Equal("Enum type 'Color' description.", schema.Description); Assert.Equal("Color", schema.Title); Assert.NotNull(schema.Enum); Assert.Equal(2, schema.Enum.Count); Assert.Equal(new string[] { "Blue", "White" }, schema.Enum.Select(e => ((OpenApiString)e).Value)); // Act string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert Assert.NotNull(json); Assert.Equal(@"{ ""title"": ""Color"", ""enum"": [ ""Blue"", ""White"" ], ""type"": ""string"", ""description"": ""Enum type 'Color' description."" }".ChangeLineBreaks(), json); } #endregion #region EdmPropertySchema [Fact] public void CreatePropertySchemaForNonNullableEnumPropertyReturnSchema() { // Arrange IEdmModel model = EdmModelHelper.BasicEdmModel; ODataContext context = new ODataContext(model); IEdmEnumType enumType = model.SchemaElements.OfType().First(e => e.Name == "Color"); EdmEntityType entitType = new EdmEntityType("NS", "Entity"); IEdmProperty property = new EdmStructuralProperty(entitType, "ColorEnumValue", new EdmEnumTypeReference(enumType, false), "yellow"); // Act var schema = context.CreatePropertySchema(property); Assert.NotNull(schema); string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert Assert.Equal(@"{ ""anyOf"": [ { ""$ref"": ""#/components/schemas/DefaultNs.Color"" } ], ""default"": ""yellow"" }".ChangeLineBreaks(), json); } [Fact] public void CreatePropertySchemaForNullableEnumPropertyReturnSchema() { // Arrange IEdmModel model = EdmModelHelper.BasicEdmModel; ODataContext context = new ODataContext(model); IEdmEnumType enumType = model.SchemaElements.OfType().First(e => e.Name == "Color"); EdmEntityType entitType = new EdmEntityType("NS", "Entity"); IEdmProperty property = new EdmStructuralProperty(entitType, "ColorEnumValue", new EdmEnumTypeReference(enumType, true), "yellow"); // Act var schema = context.CreatePropertySchema(property); Assert.NotNull(schema); string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); _output.WriteLine(json); // Assert Assert.Equal(@"{ ""anyOf"": [ { ""$ref"": ""#/components/schemas/DefaultNs.Color"" } ], ""default"": ""yellow"", ""nullable"": true }".ChangeLineBreaks(), json); } #endregion [Fact] public void NonNullableBooleanPropertyWithDefaultValueWorks() { // Arrange ODataContext context = new ODataContext(EdmModelHelper.BasicEdmModel); EdmEntityType entitType = new EdmEntityType("NS", "Entity"); IEdmStructuralProperty property = new EdmStructuralProperty( entitType, "BooleanValue", EdmCoreModel.Instance.GetBoolean(false), "false"); // Act var schema = context.CreatePropertySchema(property); // Assert Assert.NotNull(schema); Assert.Equal("boolean", schema.Type); string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); Assert.Equal(@"{ ""type"": ""boolean"", ""default"": false }".ChangeLineBreaks(), json); } [Fact] public void NonNullableBinaryPropertyWithBothMaxLengthAndDefaultValueWorks() { // Arrange ODataContext context = new ODataContext(EdmModelHelper.BasicEdmModel); EdmEntityType entitType = new EdmEntityType("NS", "Entity"); var binaryType = new EdmBinaryTypeReference(EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Binary), false, false, 44); IEdmStructuralProperty property = new EdmStructuralProperty( entitType, "BinaryValue", binaryType, "T0RhdGE"); // Act var schema = context.CreatePropertySchema(property); // Assert Assert.NotNull(schema); Assert.Equal("string", schema.Type); string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); Assert.Equal(@"{ ""maxLength"": 44, ""type"": ""string"", ""format"": ""base64url"", ""default"": ""T0RhdGE"" }".ChangeLineBreaks(), json); } [Fact] public void NonNullableIntegerPropertyWithDefaultValueWorks() { // Arrange ODataContext context = new ODataContext(EdmModelHelper.BasicEdmModel); EdmEntityType entitType = new EdmEntityType("NS", "Entity"); IEdmStructuralProperty property = new EdmStructuralProperty( entitType, "IntegerValue", EdmCoreModel.Instance.GetInt32(false), "-128"); // Act var schema = context.CreatePropertySchema(property); // Assert Assert.NotNull(schema); Assert.Equal("integer", schema.Type); string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); Assert.Equal(@"{ ""maximum"": 2147483647, ""minimum"": -2147483648, ""type"": ""integer"", ""format"": ""int32"", ""default"": -128 }".ChangeLineBreaks(), json); } [Fact] public void NonNullableDoublePropertyWithDefaultStringWorks() { // Arrange ODataContext context = new ODataContext(EdmModelHelper.BasicEdmModel); EdmEntityType entitType = new EdmEntityType("NS", "Entity"); IEdmStructuralProperty property = new EdmStructuralProperty( entitType, "DoubleValue", EdmCoreModel.Instance.GetDouble(false), "3.1415926535897931"); // Act var schema = context.CreatePropertySchema(property); // Assert Assert.NotNull(schema); Assert.Null(schema.Type); string json = schema.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); Assert.Equal(@"{ ""oneOf"": [ { ""type"": ""number"" }, { ""type"": ""string"" }, { ""enum"": [ ""-INF"", ""INF"", ""NaN"" ] } ], ""format"": ""double"", ""default"": ""3.1415926535897931"" }".ChangeLineBreaks(), json); } } }