From eb6bcb1a9d5ed204d6720656b3568c4f8c4c8066 Mon Sep 17 00:00:00 2001 From: PerthCharern Date: Fri, 19 Jan 2018 13:01:18 -0800 Subject: [PATCH] - Fix the serialization and deserialization logic for V2 formData and body parameter. - Unit tests verify that serializing, deserializing, and re-deserializing all work as expected. --- .../V2/OpenApiOperationDeserializer.cs | 43 ++- .../Models/OpenApiOperation.cs | 38 ++- .../Models/OpenApiParameter.cs | 54 +--- .../Microsoft.OpenApi.Readers.Tests.csproj | 9 + .../V2Tests/OpenApiOperationTests.cs | 286 ++++++++++++++++++ .../OpenApiOperation/basicOperation.yaml | 16 + .../OpenApiOperation/operationWithBody.yaml | 26 ++ .../operationWithFormData.yaml | 31 ++ .../Models/OpenApiDocumentTests.cs | 29 +- .../Models/OpenApiOperationTests.cs | 260 ++++++++++++++-- .../Models/OpenApiParameterTests.cs | 4 +- 11 files changed, 694 insertions(+), 102 deletions(-) create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/basicOperation.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithBody.yaml create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithFormData.yaml diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs index 22b18ab87..c7c6336bf 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs @@ -92,13 +92,15 @@ internal static partial class OpenApiV2Deserializer {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} }; - private static FixedFieldMap _responsesFixedFields = new FixedFieldMap(); + private static readonly FixedFieldMap _responsesFixedFields = + new FixedFieldMap(); - private static PatternFieldMap _responsesPatternFields = new PatternFieldMap - { - {s => !s.StartsWith("x-"), (o, p, n) => o.Add(p, LoadResponse(n))}, - {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} - }; + private static readonly PatternFieldMap _responsesPatternFields = + new PatternFieldMap + { + {s => !s.StartsWith("x-"), (o, p, n) => o.Add(p, LoadResponse(n))}, + {s => s.StartsWith("x-"), (o, p, n) => o.AddExtension(p, n.CreateAny())} + }; internal static OpenApiOperation LoadOperation(ParseNode node) { @@ -119,7 +121,7 @@ internal static OpenApiOperation LoadOperation(ParseNode node) var formParameters = node.Context.GetFromTempStorage>("formParameters"); if (formParameters != null) { - operation.RequestBody = CreateFormBody(formParameters); + operation.RequestBody = CreateFormBody(node.Context, formParameters); } } @@ -137,22 +139,33 @@ public static OpenApiResponses LoadResponses(ParseNode node) return domainObject; } - private static OpenApiRequestBody CreateFormBody(List formParameters) + private static OpenApiRequestBody CreateFormBody(ParsingContext context, List formParameters) { var mediaType = new OpenApiMediaType { Schema = new OpenApiSchema { - Properties = formParameters.ToDictionary(k => k.Name, v => v.Schema) + Properties = formParameters.ToDictionary( + k => k.Name, + v => + { + var schema = v.Schema; + schema.Description = v.Description; + return schema; + }), + Required = formParameters.Where(p => p.Required).Select(p => p.Name).ToList() } }; + var consumes = context.GetFromTempStorage>("operationconsumes") ?? + context.GetFromTempStorage>("globalconsumes") ?? + new List {"application/x-www-form-urlencoded"}; + var formBody = new OpenApiRequestBody { - Content = new Dictionary - { - {"application/x-www-form-urlencoded", mediaType} - } + Content = consumes.ToDictionary( + k => k, + v => mediaType) }; return formBody; @@ -162,8 +175,8 @@ private static OpenApiRequestBody CreateRequestBody( ParsingContext context, OpenApiParameter bodyParameter) { - var consumes = context.GetFromTempStorage>("operationproduces") ?? - context.GetFromTempStorage>("globalproduces") ?? new List {"application/json"}; + var consumes = context.GetFromTempStorage>("operationconsumes") ?? + context.GetFromTempStorage>("globalconsumes") ?? new List {"application/json"}; var requestBody = new OpenApiRequestBody { diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index 0548a447c..c8cb63668 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -222,16 +222,36 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteEndArray(); - // Create a parameter as BodyParameter type and add to Parameters. - // This type will be used to populate the In property as "body" when Parameters is serialized. - var bodyParameter = new BodyParameter + // This is form data. We need to split the request body into multiple parameters. + if (consumes.Contains("application/x-www-form-urlencoded") || + consumes.Contains("multipart/form-data")) { - Description = RequestBody.Description, - Schema = RequestBody.Content.First().Value.Schema, - Format = new List(consumes) - }; - - parameters.Add(bodyParameter); + foreach (var property in RequestBody.Content.First().Value.Schema.Properties) + { + parameters.Add( + new OpenApiFormDataParameter + { + Description = property.Value.Description, + Name = property.Key, + Schema = property.Value, + Required = RequestBody.Content.First().Value.Schema.Required.Contains(property.Key) + }); + } + } + else + { + var bodyParameter = new OpenApiBodyParameter + { + Description = RequestBody.Description, + // V2 spec actually allows the body to have custom name. + // Our library does not support this at the moment. + Name = "body", + Schema = RequestBody.Content.First().Value.Schema, + Required = RequestBody.Required + }; + + parameters.Add(bodyParameter); + } } if (Responses != null) diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index 0c0eeb7f7..a212df222 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -221,24 +221,23 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) { writer.WriteStartObject(); - // name // in - if (IsFormDataParameter()) + if (this is OpenApiFormDataParameter) { - writer.WriteProperty(OpenApiConstants.Name, "formData"); writer.WriteProperty(OpenApiConstants.In, "formData"); } - else if (IsBodyParameter()) + else if (this is OpenApiBodyParameter) { - writer.WriteProperty(OpenApiConstants.Name, "body"); writer.WriteProperty(OpenApiConstants.In, "body"); } else { - writer.WriteProperty(OpenApiConstants.Name, Name); writer.WriteProperty(OpenApiConstants.In, In.GetDisplayName()); } + // name + writer.WriteProperty(OpenApiConstants.Name, Name); + // description writer.WriteProperty(OpenApiConstants.Description, Description); @@ -249,7 +248,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false); // schema - if (IsBodyParameter()) + if (this is OpenApiBodyParameter) { writer.WriteOptionalObject(OpenApiConstants.Schema, Schema, (w, s) => s.SerializeAsV2(w)); } @@ -283,44 +282,19 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } - - private bool IsBodyParameter() - { - if (this is BodyParameter) - { - var parameter = (BodyParameter)this; - - return !( - parameter.Format.Contains("application/x-www-form-urlencoded") || - parameter.Format.Contains("multipart/form-data")); - } - - return false; - } - - private bool IsFormDataParameter() - { - if (this is BodyParameter) - { - var parameter = (BodyParameter)this; - - return - parameter.Format.Contains("application/x-www-form-urlencoded") || - parameter.Format.Contains("multipart/form-data"); - } - - return false; - } } /// /// Body parameter class to propagate information needed for /// - internal class BodyParameter : OpenApiParameter + internal class OpenApiBodyParameter : OpenApiParameter + { + } + + /// + /// Form parameter class to propagate information needed for + /// + internal class OpenApiFormDataParameter : OpenApiParameter { - /// - /// Format of the parameter. This should be the same as the "consumes" property in Operation. - /// - public IList Format { get; set; } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 0c14da164..c4f403d07 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -31,6 +31,15 @@ Never + + Never + + + Never + + + Never + Never diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs new file mode 100644 index 000000000..a52abc2d2 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Text; +using FluentAssertions; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.ParseNodes; +using Microsoft.OpenApi.Readers.V2; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests.V2Tests +{ + [Collection("DefaultSettings")] + public class OpenApiOperationTests + { + private const string SampleFolderPath = "V2Tests/Samples/OpenApiOperation/"; + + private static readonly OpenApiOperation _basicOperation = new OpenApiOperation + { + Summary = "Updates a pet in the store", + Description = "", + OperationId = "updatePet", + Parameters = new List + { + new OpenApiParameter + { + Name = "petId", + In = ParameterLocation.Path, + Description = "ID of pet that needs to be updated", + Required = true, + Schema = new OpenApiSchema + { + Type = "string" + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Pet updated." + } + } + }; + + private static readonly OpenApiOperation _operationWithFormData = + new OpenApiOperation + { + Summary = "Updates a pet in the store with form data", + Description = "", + OperationId = "updatePetWithForm", + Parameters = new List + { + new OpenApiParameter + { + Name = "petId", + In = ParameterLocation.Path, + Description = "ID of pet that needs to be updated", + Required = true, + Schema = new OpenApiSchema + { + Type = "string" + } + } + }, + RequestBody = new OpenApiRequestBody + { + Content = + { + ["application/x-www-form-urlencoded"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Properties = + { + ["name"] = new OpenApiSchema + { + Description = "Updated name of the pet", + Type = "string" + }, + ["status"] = new OpenApiSchema + { + Description = "Updated status of the pet", + Type = "string" + } + }, + Required = new List + { + "name" + } + } + }, + ["multipart/form-data"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Properties = + { + ["name"] = new OpenApiSchema + { + Description = "Updated name of the pet", + Type = "string" + }, + ["status"] = new OpenApiSchema + { + Description = "Updated status of the pet", + Type = "string" + } + }, + Required = new List + { + "name" + } + } + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Pet updated." + }, + ["405"] = new OpenApiResponse + { + Description = "Invalid input" + } + } + }; + + private static readonly OpenApiOperation _operationWithBody = new OpenApiOperation + { + Summary = "Updates a pet in the store with request body", + Description = "", + OperationId = "updatePetWithBody", + Parameters = new List + { + new OpenApiParameter + { + Name = "petId", + In = ParameterLocation.Path, + Description = "ID of pet that needs to be updated", + Required = true, + Schema = new OpenApiSchema + { + Type = "string" + } + }, + }, + RequestBody = new OpenApiRequestBody + { + Description = "Pet to update with", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "object" + } + } + } + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "Pet updated." + }, + ["405"] = new OpenApiResponse + { + Description = "Invalid input" + } + }, + }; + + [Fact] + public void ParseBasicOperationShouldSucceed() + { + // Arrange + MapNode node; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicOperation.yaml"))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + + // Assert + operation.ShouldBeEquivalentTo(_basicOperation); + } + + [Fact] + public void ParseBasicOperationTwiceShouldYieldSameObject() + { + // Arrange + MapNode node; + using (var stream = new MemoryStream( + Encoding.Default.GetBytes(_basicOperation.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0)))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + + // Assert + operation.ShouldBeEquivalentTo(_basicOperation); + } + + [Fact] + public void ParseOperationWithFormDataShouldSucceed() + { + // Arrange + MapNode node; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithFormData.yaml"))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + + // Assert + operation.ShouldBeEquivalentTo(_operationWithFormData); + } + + [Fact] + public void ParseOperationWithFormDataTwiceShouldYieldSameObject() + { + // Arrange + MapNode node; + using (var stream = new MemoryStream( + Encoding.Default.GetBytes(_operationWithFormData.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0)))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + + // Assert + operation.ShouldBeEquivalentTo(_operationWithFormData); + } + + [Fact] + public void ParseOperationWithBodyShouldSucceed() + { + // Arrange + MapNode node; + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithBody.yaml"))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + + // Assert + operation.ShouldBeEquivalentTo(_operationWithBody); + } + + [Fact] + public void ParseOperationWithBodyTwiceShouldYieldSameObject() + { + // Arrange + MapNode node; + using (var stream = new MemoryStream( + Encoding.Default.GetBytes(_operationWithBody.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0)))) + { + node = TestHelper.CreateYamlMapNode(stream); + } + + // Act + var operation = OpenApiV2Deserializer.LoadOperation(node); + + // Assert + operation.ShouldBeEquivalentTo(_operationWithBody); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/basicOperation.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/basicOperation.yaml new file mode 100644 index 000000000..1623afbff --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/basicOperation.yaml @@ -0,0 +1,16 @@ +# Modified from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object-example +summary: Updates a pet in the store +description: "" +operationId: updatePet +produces: +- application/json +- application/xml +parameters: +- name: petId + in: path + description: ID of pet that needs to be updated + required: true + type: string +responses: + '200': + description: Pet updated. \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithBody.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithBody.yaml new file mode 100644 index 000000000..c643a3117 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithBody.yaml @@ -0,0 +1,26 @@ +# Modified from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object-example +summary: Updates a pet in the store with request body +description: "" +operationId: updatePetWithBody +consumes: +- application/json +produces: +- application/json +- application/xml +parameters: +- name: petId + in: path + description: ID of pet that needs to be updated + required: true + type: string +- name: petObject + in: body + description: Pet to update with + required: true + schema: + type: object +responses: + '200': + description: Pet updated. + '405': + description: Invalid input diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithFormData.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithFormData.yaml new file mode 100644 index 000000000..31f2ec5fe --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithFormData.yaml @@ -0,0 +1,31 @@ +# Modified from https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object-example +summary: Updates a pet in the store with form data +description: "" +operationId: updatePetWithForm +consumes: +- application/x-www-form-urlencoded +- multipart/form-data +produces: +- application/json +- application/xml +parameters: +- name: petId + in: path + description: ID of pet that needs to be updated + required: true + type: string +- name: name + in: formData + description: Updated name of the pet + required: true + type: string +- name: status + in: formData + description: Updated status of the pet + required: false + type: string +responses: + '200': + description: Pet updated. + '405': + description: Invalid input \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index 879d3d3a3..929bb29ec 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -1726,8 +1726,7 @@ public void SerializeAdvancedDocumentAsV2JsonWorks() // Arrange var outputStringWriter = new StringWriter(); var writer = new OpenApiJsonWriter(outputStringWriter); - var expected = - @"{ + var expected = @"{ ""swagger"": ""2.0"", ""info"": { ""title"": ""Swagger Petstore (Simple)"", @@ -1761,8 +1760,8 @@ public void SerializeAdvancedDocumentAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""tags"", ""in"": ""query"", + ""name"": ""tags"", ""description"": ""tags to filter by"", ""type"": ""array"", ""items"": { @@ -1770,8 +1769,8 @@ public void SerializeAdvancedDocumentAsV2JsonWorks() } }, { - ""name"": ""limit"", ""in"": ""query"", + ""name"": ""limit"", ""description"": ""maximum number of results to return"", ""type"": ""integer"", ""format"": ""int32"" @@ -1855,9 +1854,10 @@ public void SerializeAdvancedDocumentAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""body"", ""in"": ""body"", + ""name"": ""body"", ""description"": ""Pet to add to the store"", + ""required"": true, ""schema"": { ""required"": [ ""name"" @@ -1953,8 +1953,8 @@ public void SerializeAdvancedDocumentAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""id"", ""in"": ""path"", + ""name"": ""id"", ""description"": ""ID of pet to fetch"", ""required"": true, ""type"": ""integer"", @@ -2032,8 +2032,8 @@ public void SerializeAdvancedDocumentAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""id"", ""in"": ""path"", + ""name"": ""id"", ""description"": ""ID of pet to delete"", ""required"": true, ""type"": ""integer"", @@ -2147,7 +2147,7 @@ public void SerializeAdvancedDocumentAsV2JsonWorks() AdvancedDocument.SerializeAsV2(writer); writer.Flush(); var actual = outputStringWriter.GetStringBuilder().ToString(); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -2195,8 +2195,8 @@ public void SerializeAdvancedDocumentWithReferenceAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""tags"", ""in"": ""query"", + ""name"": ""tags"", ""description"": ""tags to filter by"", ""type"": ""array"", ""items"": { @@ -2204,8 +2204,8 @@ public void SerializeAdvancedDocumentWithReferenceAsV2JsonWorks() } }, { - ""name"": ""limit"", ""in"": ""query"", + ""name"": ""limit"", ""description"": ""maximum number of results to return"", ""type"": ""integer"", ""format"": ""int32"" @@ -2247,9 +2247,10 @@ public void SerializeAdvancedDocumentWithReferenceAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""body"", ""in"": ""body"", + ""name"": ""body"", ""description"": ""Pet to add to the store"", + ""required"": true, ""schema"": { ""$ref"": ""#/definitions/newPet"" } @@ -2288,8 +2289,8 @@ public void SerializeAdvancedDocumentWithReferenceAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""id"", ""in"": ""path"", + ""name"": ""id"", ""description"": ""ID of pet to fetch"", ""required"": true, ""type"": ""integer"", @@ -2325,8 +2326,8 @@ public void SerializeAdvancedDocumentWithReferenceAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""id"", ""in"": ""path"", + ""name"": ""id"", ""description"": ""ID of pet to delete"", ""required"": true, ""type"": ""integer"", @@ -2414,7 +2415,7 @@ public void SerializeAdvancedDocumentWithReferenceAsV2JsonWorks() AdvancedDocumentWithReference.SerializeAsV2(writer); writer.Flush(); var actual = outputStringWriter.GetStringBuilder().ToString(); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs index 81fe73f37..31a3b9976 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs @@ -14,9 +14,9 @@ namespace Microsoft.OpenApi.Tests.Models [Collection("DefaultSettings")] public class OpenApiOperationTests { - public static OpenApiOperation BasicOperation = new OpenApiOperation(); + private static readonly OpenApiOperation _basicOperation = new OpenApiOperation(); - public static OpenApiOperation AdvancedOperation = new OpenApiOperation + private static readonly OpenApiOperation _operationWithBody = new OpenApiOperation { Summary = "summary1", Description = "operationDescription", @@ -92,7 +92,7 @@ public class OpenApiOperationTests } }; - public static OpenApiOperation AdvancedOperationWithTagsAndSecurity = new OpenApiOperation + private static readonly OpenApiOperation _advancedOperationWithTagsAndSecurity = new OpenApiOperation { Tags = new List { @@ -210,6 +210,91 @@ [new OpenApiSecurityScheme } }; + private static readonly OpenApiOperation _operationWithFormData = + new OpenApiOperation() + { + Summary = "Updates a pet in the store with form data", + Description = "", + OperationId = "updatePetWithForm", + Parameters = new List() + { + new OpenApiParameter() + { + Name = "petId", + In = ParameterLocation.Path, + Description = "ID of pet that needs to be updated", + Required = true, + Schema = new OpenApiSchema() + { + Type = "string" + } + } + }, + RequestBody = new OpenApiRequestBody() + { + Content = + { + ["application/x-www-form-urlencoded"] = new OpenApiMediaType() + { + Schema = new OpenApiSchema() + { + Properties = + { + ["name"] = new OpenApiSchema() + { + Description = "Updated name of the pet", + Type = "string" + }, + ["status"] = new OpenApiSchema() + { + Description = "Updated status of the pet", + Type = "string" + } + }, + Required = new List() + { + "name" + } + } + }, + ["multipart/form-data"] = new OpenApiMediaType() + { + Schema = new OpenApiSchema() + { + Properties = + { + ["name"] = new OpenApiSchema() + { + Description = "Updated name of the pet", + Type = "string" + }, + ["status"] = new OpenApiSchema() + { + Description = "Updated status of the pet", + Type = "string" + } + }, + Required = new List() + { + "name" + } + } + } + } + }, + Responses = new OpenApiResponses() + { + ["200"] = new OpenApiResponse() + { + Description = "Pet updated." + }, + ["405"] = new OpenApiResponse() + { + Description = "Invalid input" + } + } + }; + private readonly ITestOutputHelper _output; public OpenApiOperationTests(ITestOutputHelper output) @@ -226,7 +311,7 @@ public void SerializeBasicOperationAsV3JsonWorks() }"; // Act - var actual = BasicOperation.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + var actual = _basicOperation.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -235,7 +320,7 @@ public void SerializeBasicOperationAsV3JsonWorks() } [Fact] - public void SerializeAdvancedOperationAsV3JsonWorks() + public void SerializeOperationWithBodyAsV3JsonWorks() { // Arrange var expected = @"{ @@ -294,7 +379,7 @@ public void SerializeAdvancedOperationAsV3JsonWorks() }"; // Act - var actual = AdvancedOperation.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + var actual = _operationWithBody.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -375,7 +460,7 @@ public void SerializeAdvancedOperationWithTagAndSecurityAsV3JsonWorks() }"; // Act - var actual = AdvancedOperationWithTagsAndSecurity.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + var actual = _advancedOperationWithTagsAndSecurity.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -392,7 +477,7 @@ public void SerializeBasicOperationAsV2JsonWorks() }"; // Act - var actual = BasicOperation.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); + var actual = _basicOperation.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -401,7 +486,136 @@ public void SerializeBasicOperationAsV2JsonWorks() } [Fact] - public void SerializeAdvancedOperationAsV2JsonWorks() + public void SerializeOperationWithFormDataAsV3JsonWorks() + { + // Arrange + var expected = @"{ + ""summary"": ""Updates a pet in the store with form data"", + ""description"": """", + ""operationId"": ""updatePetWithForm"", + ""parameters"": [ + { + ""name"": ""petId"", + ""in"": ""path"", + ""description"": ""ID of pet that needs to be updated"", + ""required"": true, + ""schema"": { + ""type"": ""string"" + } + } + ], + ""requestBody"": { + ""content"": { + ""application/x-www-form-urlencoded"": { + ""schema"": { + ""required"": [ + ""name"" + ], + ""properties"": { + ""name"": { + ""type"": ""string"", + ""description"": ""Updated name of the pet"" + }, + ""status"": { + ""type"": ""string"", + ""description"": ""Updated status of the pet"" + } + } + } + }, + ""multipart/form-data"": { + ""schema"": { + ""required"": [ + ""name"" + ], + ""properties"": { + ""name"": { + ""type"": ""string"", + ""description"": ""Updated name of the pet"" + }, + ""status"": { + ""type"": ""string"", + ""description"": ""Updated status of the pet"" + } + } + } + } + } + }, + ""responses"": { + ""200"": { + ""description"": ""Pet updated."" + }, + ""405"": { + ""description"": ""Invalid input"" + } + } +}"; + + // Act + var actual = _operationWithFormData.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeOperationWithFormDataAsV2JsonWorks() + { + // Arrange + var expected = @"{ + ""summary"": ""Updates a pet in the store with form data"", + ""description"": """", + ""operationId"": ""updatePetWithForm"", + ""consumes"": [ + ""application/x-www-form-urlencoded"", + ""multipart/form-data"" + ], + ""parameters"": [ + { + ""in"": ""path"", + ""name"": ""petId"", + ""description"": ""ID of pet that needs to be updated"", + ""required"": true, + ""type"": ""string"" + }, + { + ""in"": ""formData"", + ""name"": ""name"", + ""description"": ""Updated name of the pet"", + ""required"": true, + ""type"": ""string"" + }, + { + ""in"": ""formData"", + ""name"": ""status"", + ""description"": ""Updated status of the pet"", + ""type"": ""string"" + } + ], + ""responses"": { + ""200"": { + ""description"": ""Pet updated."" + }, + ""405"": { + ""description"": ""Invalid input"" + } + } +}"; + + // Act + var actual = _operationWithFormData.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeOperationWithBodyAsV2JsonWorks() { // Arrange var expected = @"{ @@ -420,17 +634,18 @@ public void SerializeAdvancedOperationAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""parameter1"", - ""in"": ""path"" + ""in"": ""path"", + ""name"": ""parameter1"" }, { - ""name"": ""parameter2"", - ""in"": ""header"" + ""in"": ""header"", + ""name"": ""parameter2"" }, { - ""name"": ""body"", ""in"": ""body"", + ""name"": ""body"", ""description"": ""description2"", + ""required"": true, ""schema"": { ""maximum"": 10, ""minimum"": 5, @@ -456,8 +671,8 @@ public void SerializeAdvancedOperationAsV2JsonWorks() }"; // Act - var actual = AdvancedOperation.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); - + var actual = _operationWithBody.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -488,17 +703,18 @@ public void SerializeAdvancedOperationWithTagAndSecurityAsV2JsonWorks() ], ""parameters"": [ { - ""name"": ""parameter1"", - ""in"": ""path"" + ""in"": ""path"", + ""name"": ""parameter1"" }, { - ""name"": ""parameter2"", - ""in"": ""header"" + ""in"": ""header"", + ""name"": ""parameter2"" }, { - ""name"": ""body"", ""in"": ""body"", + ""name"": ""body"", ""description"": ""description2"", + ""required"": true, ""schema"": { ""maximum"": 10, ""minimum"": 5, @@ -533,7 +749,7 @@ public void SerializeAdvancedOperationWithTagAndSecurityAsV2JsonWorks() }"; // Act - var actual = AdvancedOperationWithTagsAndSecurity.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); + var actual = _advancedOperationWithTagsAndSecurity.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs index 90400f57f..f7d2aca56 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs @@ -189,8 +189,8 @@ public void SerializeReferencedParameterAsV2JsonWithoutReferenceWorks() var writer = new OpenApiJsonWriter(outputStringWriter); var expected = @"{ - ""name"": ""name1"", - ""in"": ""path"" + ""in"": ""path"", + ""name"": ""name1"" }"; // Act