From 57ceb56459482874b9cda7a18f0e2d7b25447a14 Mon Sep 17 00:00:00 2001 From: Tibor Takacs Date: Thu, 23 Apr 2020 16:09:49 +0100 Subject: [PATCH 1/4] Add predefined operationId support. If the operation described in the XML file has an tag, it is used, otherwise (keeping the original behaviour) the id is generated using the path () and the operation type (). In order to do this, the following changes have been introduced: + Add UsePredefinedOperationIdFilter class which creates operations using tag. + Add CreateOperationMetaFilter which executes a list of operation creation filters. The first successful filter is used to generate the operations. This mechanism is used to support both the predefined and the generated operation id: first, the predefined operation id based filter is used, and if it fails, the operation id generation based filter is executed. + Add ICreateOperationPreprocessingOperationFilter to support applicable check in operation create filters in a general way. + Extend OperationHandler with GetOperationId function. + Slight refactor. + Small fix in GetUrl function. + Add extensive unit tests + Remove duplicated code from BranchOptionalPathParametersFilter and use OperationHandler existing functionality. --- .../Exceptions/InvalidOperationIdException.cs | 24 + .../FilterSet.cs | 2 +- .../InternalOpenApiGenerator.cs | 2 +- .../Models/KnownStrings/KnownXmlStrings.cs | 1 + .../OperationHandler.cs | 45 +- .../BranchOptionalPathParametersFilter.cs | 38 +- .../CreateOperationMetaFilter.cs | 82 ++ ...teOperationPreProcessingOperationFilter.cs | 28 + .../UsePredefinedOperationIdFilter.cs | 112 +++ .../SpecificationGenerationMessages.cs | 4 + .../CreateOperationMetaFilterTest.cs | 239 ++++++ .../UsePredefinedOperationIdFilterTest.cs | 389 +++++++++ ...nnotations.DocumentGeneration.Tests.csproj | 13 + ...ationPredefinedAndGeneratedOperationId.xml | 249 ++++++ .../Input/AnnotationPredefinedOperationId.xml | 254 ++++++ .../OpenApiDocumentGeneratorTest.cs | 50 ++ ...tionPredefinedAndGeneratedOperationId.Json | 758 ++++++++++++++++++ .../AnnotationPredefinedOperationId.Json | 758 ++++++++++++++++++ .../OperationHandler_GetUrlTests.cs | 113 +++ .../OperationHandler_OperationIdTests.cs | 146 ++++ .../OperationHandler_OperationMethodTests.cs | 91 +++ 21 files changed, 3370 insertions(+), 28 deletions(-) create mode 100644 src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Exceptions/InvalidOperationIdException.cs create mode 100644 src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs create mode 100644 src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/ICreateOperationPreProcessingOperationFilter.cs create mode 100644 src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs create mode 100644 test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/CreateOperationMetaFilterTest.cs create mode 100644 test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/UsePredefinedOperationIdFilterTest.cs create mode 100644 test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Input/AnnotationPredefinedAndGeneratedOperationId.xml create mode 100644 test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Input/AnnotationPredefinedOperationId.xml create mode 100644 test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Output/AnnotationPredefinedAndGeneratedOperationId.Json create mode 100644 test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Output/AnnotationPredefinedOperationId.Json create mode 100644 test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_GetUrlTests.cs create mode 100644 test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationIdTests.cs create mode 100644 test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationMethodTests.cs diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Exceptions/InvalidOperationIdException.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Exceptions/InvalidOperationIdException.cs new file mode 100644 index 0000000..c48d7f0 --- /dev/null +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Exceptions/InvalidOperationIdException.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Exceptions +{ + /// + /// The exception that is recorded when it is expected to have operationId XML tag + /// but it is missing or there are more than one tags. + /// + [Serializable] + internal class InvalidOperationIdException : DocumentationException + { + /// + /// Initializes a new instance of the . + /// + /// Error message. + public InvalidOperationIdException(string message = "") + : base(message) + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/FilterSet.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/FilterSet.cs index 968e15f..1989ce8 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/FilterSet.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/FilterSet.cs @@ -113,7 +113,7 @@ public static FilterSet GetDefaultFilterSet(FilterSetVersion version) _defaultFilterSet.Add(new PopulateInAttributeFilter()); _defaultFilterSet.Add(new ConvertAlternativeParamTagsFilter()); _defaultFilterSet.Add(new ValidateInAttributeFilter()); - _defaultFilterSet.Add(new BranchOptionalPathParametersFilter()); + _defaultFilterSet.Add(new CreateOperationMetaFilter()); //Post processing document filters _defaultFilterSet.Add(new RemoveFailedGenerationOperationFilter()); diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/InternalOpenApiGenerator.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/InternalOpenApiGenerator.cs index da7d3d9..a9b3ed7 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/InternalOpenApiGenerator.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/InternalOpenApiGenerator.cs @@ -501,7 +501,7 @@ private IList GenerateSpecificationDocuments( try { - operationMethod = OperationHandler.GetOperationMethod(url, operationElement); + operationMethod = OperationHandler.GetOperationMethod(operationElement); } catch (InvalidVerbException e) { diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Models/KnownStrings/KnownXmlStrings.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Models/KnownStrings/KnownXmlStrings.cs index 5f6d84e..903f87e 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Models/KnownStrings/KnownXmlStrings.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Models/KnownStrings/KnownXmlStrings.cs @@ -64,6 +64,7 @@ public class KnownXmlStrings public const string AuthorizationCode = "authorizationCode"; public const string ClientCredentials = "clientCredentials"; public const string Scope = "scope"; + public const string OperationId = "operationId"; public static string[] AllowedAppKeyInValues => new[] {Header, Query, Cookie}; public static string[] AllowedFlowTypeValues => new[] diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/OperationHandler.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/OperationHandler.cs index 329eab2..62f1ecd 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/OperationHandler.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/OperationHandler.cs @@ -19,9 +19,42 @@ namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration internal static class OperationHandler { /// - /// Gets the operation id by parsing segments out of the absolute path. + /// Checks whether the optional operation id tag is present in the operation element. /// - public static string GetOperationId(string absolutePath, OperationType operationMethod) + /// + /// True if the operationId tag is in the operation element, otherwise false. + public static bool HasOperationId(XElement operationElement) + { + return operationElement?.Elements().Where(p => p.Name == KnownXmlStrings.OperationId).Count() > 0; + } + + /// + /// Extracts the operation id from the operation element. + /// + /// + /// Operation id. + /// Thrown if the operationId tag is missing or + /// there are more than one tags. + public static string GetOperationId(XElement operationElement) + { + var operationIdList = operationElement?.Elements().Where(p => p.Name == KnownXmlStrings.OperationId).ToList(); + if (operationIdList?.Count == 1) + { + return operationIdList[0].Value; + } + else + { + string error = operationIdList.Count > 1 + ? SpecificationGenerationMessages.MultipleOperationId + : SpecificationGenerationMessages.NoOperationId; + throw new InvalidOperationIdException(error); + } + } + + /// + /// Generates the operation id by parsing segments out of the absolute path. + /// + public static string GenerateOperationId(string absolutePath, OperationType operationMethod) { var operationId = new StringBuilder(operationMethod.ToString().ToLowerInvariant()); @@ -63,10 +96,9 @@ public static string GetOperationId(string absolutePath, OperationType operation /// /// Extracts the operation method from the operation element /// + /// The xml element representing an operation in the annotation xml. /// Thrown if the verb is missing or has invalid format. - public static OperationType GetOperationMethod( - string url, - XElement operationElement) + public static OperationType GetOperationMethod(XElement operationElement) { var verbElement = operationElement.Descendants().FirstOrDefault(i => i.Name == KnownXmlStrings.Verb); @@ -85,6 +117,7 @@ public static OperationType GetOperationMethod( /// /// Extracts the URL from the operation element /// + /// The xml element representing an operation in the annotation xml. /// Thrown if the URL is missing or has invalid format. public static string GetUrl( XElement operationElement) @@ -103,7 +136,7 @@ public static string GetUrl( try { - url = WebUtility.UrlDecode(new Uri(url).AbsolutePath); + url = WebUtility.UrlDecode(new Uri(WebUtility.UrlDecode(url)).AbsolutePath); } catch (UriFormatException) { diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/BranchOptionalPathParametersFilter.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/BranchOptionalPathParametersFilter.cs index ad82087..ad40a52 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/BranchOptionalPathParametersFilter.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/BranchOptionalPathParametersFilter.cs @@ -19,8 +19,18 @@ namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.PreprocessingOp /// Parses the value of the URL and creates multiple operations in the Paths object when /// there are optional path parameters. /// - public class BranchOptionalPathParametersFilter : IPreProcessingOperationFilter + public class BranchOptionalPathParametersFilter : ICreateOperationPreProcessingOperationFilter { + /// + /// Verifies that the annotation XML element contains all data which are required to apply this filter. + /// + /// The xml element representing an operation in the annotation xml. + /// Always true (this filter can be always applied). + public bool IsApplicable(XElement element) + { + return true; + } + /// /// Fetches the URL value and creates multiple operations based on optional parameters. /// @@ -37,30 +47,18 @@ public IList Apply( try { - var paramElements = element.Elements() + var paramPathElements = element.Elements() .Where( p => p.Name == KnownXmlStrings.Param) + .Where( + p => p.Attribute(KnownXmlStrings.In)?.Value == KnownXmlStrings.Path) .ToList(); - // We need both the full URL and the absolute paths for processing. - // Full URL contains all path and query parameters. - // Absolute path is needed to get OperationId parsed out correctly. - var fullUrl = element.Elements() - .FirstOrDefault(p => p.Name == KnownXmlStrings.Url) - ?.Value; - - var absolutePath = fullUrl.UrlStringToAbsolutePath(); + var absolutePath = OperationHandler.GetUrl(element); - var operationMethod = (OperationType)Enum.Parse( - typeof(OperationType), - element.Elements().FirstOrDefault(p => p.Name == KnownXmlStrings.Verb)?.Value, - ignoreCase: true); + var allGeneratedPathStrings = GeneratePossiblePaths(absolutePath, paramPathElements); - var allGeneratedPathStrings = GeneratePossiblePaths( - absolutePath, - paramElements.Where( - p => p.Attribute(KnownXmlStrings.In)?.Value == KnownXmlStrings.Path) - .ToList()); + var operationMethod = OperationHandler.GetOperationMethod(element); foreach (var pathString in allGeneratedPathStrings) { @@ -72,7 +70,7 @@ public IList Apply( paths[pathString].Operations[operationMethod] = new OpenApiOperation { - OperationId = OperationHandler.GetOperationId(pathString, operationMethod) + OperationId = OperationHandler.GenerateOperationId(pathString, operationMethod) }; } } diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs new file mode 100644 index 0000000..fb86148 --- /dev/null +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs @@ -0,0 +1,82 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Models; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.PreprocessingOperationFilters +{ + /// + /// Filter to initialize OpenApi operation based on the annotation XML element. + /// + /// It does not do the creation itself but forwards the call to the real generator filters. + /// The first filter which is applicable is executed in order to generate the operation. + /// + public class CreateOperationMetaFilter : IPreProcessingOperationFilter + { + private List createOperationFilters; + + /// + /// Initializes a new production instance of the . + /// + /// + /// Using this constructor, the following filter list is used: + /// If the XML element contains 'operationId' tag, it is used as unique identifier + /// of the operation. Otherwise, the id is generated using the path of the operation. + /// In the latter case, multiple operation could be generated, if the path has optional + /// parameters. + /// + public CreateOperationMetaFilter() : + this(new List { + new UsePredefinedOperationIdFilter(), new BranchOptionalPathParametersFilter()}) + { + } + + /// + /// Initializes a new instance of the . + /// + /// List of generator filters. + internal CreateOperationMetaFilter( + List createOperationFilters) + { + this.createOperationFilters = createOperationFilters; + } + + /// + /// Initializes the OpenApi operation based on the annotation XML element. + /// + /// + /// The paths to be updated. + /// The xml element representing an operation in the annotation xml. + /// The operation filter settings. + /// The list of generation errors, if any produced when processing the filter. + public IList Apply( + OpenApiPaths paths, + XElement element, + PreProcessingOperationFilterSettings settings) + { + foreach (var filter in this.createOperationFilters) + { + if (filter.IsApplicable(element)) + { + return filter.Apply(paths, element, settings); + } + } + + // If none of the filters could be applied --> error + return new List + { + new GenerationError + { + Message = "Failed to apply any operation creation filter.", + ExceptionType = nameof(InvalidOperationException) + } + }; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/ICreateOperationPreProcessingOperationFilter.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/ICreateOperationPreProcessingOperationFilter.cs new file mode 100644 index 0000000..3e84540 --- /dev/null +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/ICreateOperationPreProcessingOperationFilter.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Xml.Linq; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Models; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.OperationFilters; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.PreprocessingOperationFilters +{ + /// + /// The class representing the contract of a filter to preprocess the + /// objects in before each is processed by the + /// . + /// + public interface ICreateOperationPreProcessingOperationFilter : IPreProcessingOperationFilter + { + /// + /// Verifies that the annotation XML element contains all data which are required to apply this filter. + /// + /// The xml element representing an operation in the annotation xml. + /// True if the filter can be applied, otherwise false. + bool IsApplicable(XElement element); + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs new file mode 100644 index 0000000..41b1775 --- /dev/null +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs @@ -0,0 +1,112 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Models; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.PreprocessingOperationFilters +{ + /// + /// Filter to creates operation using the 'operationId' tag of the operation. + /// It does not consider optional parameters, it create one operation for the full path. + /// + public class UsePredefinedOperationIdFilter : ICreateOperationPreProcessingOperationFilter + { + private readonly Func GetUrlFunc; + private readonly Func GetOperationMethodFunc; + private readonly Func GetOperationIdFunc; + private readonly Predicate HasOperationIdFunc; + + /// + /// Initializes a new production instance of the . + /// + public UsePredefinedOperationIdFilter() + : this( + OperationHandler.GetUrl, + OperationHandler.GetOperationMethod, + OperationHandler.GetOperationId, + OperationHandler.HasOperationId) + { + } + + /// + /// Initializes a new instance of the . + /// + /// Get url function. + /// Get operation method function. + /// Get operation id function. + /// Has operation id function. + public UsePredefinedOperationIdFilter( + Func GetUrlFunc, + Func GetOperationMethodFunc, + Func GetOperationIdFunc, + Predicate HasOperationIdFunc) + { + this.GetUrlFunc = GetUrlFunc; + this.GetOperationMethodFunc = GetOperationMethodFunc; + this.GetOperationIdFunc = GetOperationIdFunc; + this.HasOperationIdFunc = HasOperationIdFunc; + } + + /// + /// Verifies whether the annotation XML element contains operationId XML tag. + /// + /// The xml element representing an operation in the annotation xml. + /// + public bool IsApplicable(XElement element) + { + return this.HasOperationIdFunc(element); + } + + /// + /// Creates the operation in the Paths object using the operationId tag of the operation. + /// It does not consider optional parameters, it create one operation for the full path. + /// the URL value and creates multiple operations based on optional parameters. + /// + /// The paths to be updated. + /// The xml element representing an operation in the annotation xml. + /// The operation filter settings. + /// The list of generation errors, if any produced when processing the filter. + public IList Apply( + OpenApiPaths paths, + XElement element, + PreProcessingOperationFilterSettings settings) + { + var generationErrors = new List(); + + try + { + var absolutePath = this.GetUrlFunc(element); + var operationMethod = this.GetOperationMethodFunc(element); + string operationId = this.GetOperationIdFunc(element); + + if (!paths.ContainsKey(absolutePath)) + { + paths[absolutePath] = new OpenApiPathItem(); + } + + paths[absolutePath].Operations[operationMethod] = + new OpenApiOperation + { + OperationId = operationId + }; + } + catch(Exception ex) + { + generationErrors.Add( + new GenerationError + { + Message = ex.Message, + ExceptionType = ex.GetType().Name + }); + } + + return generationErrors; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/SpecificationGenerationMessages.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/SpecificationGenerationMessages.cs index d83b683..328ab8d 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/SpecificationGenerationMessages.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/SpecificationGenerationMessages.cs @@ -123,5 +123,9 @@ public static class SpecificationGenerationMessages public const string NotSupportedTypeAttributeValue = "Documented type: \"{0}\" is not supported for \"type\" attribute value in tag: \"{1}\". Supported values are: {2}."; + + public const string NoOperationId = "No operationId element is present in the operation."; + + public const string MultipleOperationId = "More than one operationId element is not accepted in the operation."; } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/CreateOperationMetaFilterTest.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/CreateOperationMetaFilterTest.cs new file mode 100644 index 0000000..cb90d2c --- /dev/null +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/CreateOperationMetaFilterTest.cs @@ -0,0 +1,239 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using FluentAssertions; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Models; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.PreprocessingOperationFilters; +using Microsoft.OpenApi.Models; +using Moq; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.FilterTests +{ + [Collection("DefaultSettings")] + public class CreateOperationMetaFilterTest + { + private readonly ITestOutputHelper _output; + + public CreateOperationMetaFilterTest(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + [MemberData(nameof(GetTestCasesForApplyShouldCallOnlyTheFirstFilterWhichIsApplicable))] + public void ApplyShouldCallOnlyTheFirstFilterWhichIsApplicable( + string testName, + List> mockFilters, + int firstApplicableFilterIndex) + { + _output.WriteLine(testName); + + // Prepare + var openApiPaths = new OpenApiPaths(); + XElement element = new XElement("elem"); + var settings = new PreProcessingOperationFilterSettings(); + + List filters = + mockFilters.Select(mockFilter => mockFilter.Object).ToList(); + + var filter = new CreateOperationMetaFilter(filters); + + // Action + filter.Apply(openApiPaths, element, settings); + + // Assert + for(int i = 0; i < firstApplicableFilterIndex - 1; ++i) + { + mockFilters[i].Verify(mock => mock.Apply( + It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never()); + } + + for (int i = firstApplicableFilterIndex + 1; i < mockFilters.Count; ++i) + { + mockFilters[i].Verify(mock => mock.Apply( + It.IsAny(), It.IsAny(), It.IsAny()), + Times.Never()); + } + + mockFilters[firstApplicableFilterIndex].Verify(mock => mock.Apply( + openApiPaths, element, settings), Times.Once()); + } + + [Fact] + public void ApplyShouldReturnErrorIfNoneOfTheFiltersCouldBeApplied() + { + // Prepare + var openApiPaths = new OpenApiPaths(); + XElement element = new XElement("elem"); + var settings = new PreProcessingOperationFilterSettings(); + + var mockFilters = new List>{ + CreateMockFilter(false), + CreateMockFilter(false), + CreateMockFilter(false)}; + + List filters = + mockFilters.Select(mockFilter => mockFilter.Object).ToList(); + + var filter = new CreateOperationMetaFilter(filters); + + // Action + var errorList = filter.Apply(openApiPaths, element, settings); + + // Assert + errorList.Should().OnlyContain(error => error.ExceptionType == "InvalidOperationException"); + } + + [Fact] + public void ApplyShouldNotReturnErrorIfTheAppliedFilterDoesNotReturnError() + { + // Prepare + var openApiPaths = new OpenApiPaths(); + XElement element = new XElement("elem"); + var settings = new PreProcessingOperationFilterSettings(); + + var successfullFilter = CreateMockFilter(true); + + var mockFilters = new List>{ + successfullFilter}; + + List filters = + mockFilters.Select(mockFilter => mockFilter.Object).ToList(); + + var filter = new CreateOperationMetaFilter(filters); + + // Action + var errorList = filter.Apply(openApiPaths, element, settings); + + // Assert + errorList.Should().BeEmpty(); + } + + [Fact] + public void ApplyShouldReturnTheErrorListOFTheAppliedFilter() + { + // Prepare + var openApiPaths = new OpenApiPaths(); + XElement element = new XElement("elem"); + var settings = new PreProcessingOperationFilterSettings(); + + var mockFilterWithError = new Mock(); + + var expectedErrorList = new List + { + new GenerationError + { + Message = "Message_1", + ExceptionType = "ExceptionType_1" + }, + new GenerationError + { + Message = "Message_2", + ExceptionType = "ExceptionType_2" + } + }; + + mockFilterWithError.Setup( + mock => mock.Apply( + It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(expectedErrorList); + + mockFilterWithError.Setup(mock => mock.IsApplicable(It.IsAny())).Returns(true); + + var mockFilters = new List> + { + mockFilterWithError + }; + + List filters = + mockFilters.Select(mockFilter => mockFilter.Object).ToList(); + + // Action + var filter = new CreateOperationMetaFilter(filters); + var errorList = filter.Apply(openApiPaths, element, settings); + + // Assert + errorList.Should().ContainInOrder(expectedErrorList); + } + + public static IEnumerable GetTestCasesForApplyShouldCallOnlyTheFirstFilterWhichIsApplicable() + { + yield return new object[] + { + "Only the first filter is applicable", + new List>{ + CreateMockFilter(true), + CreateMockFilter(false), + CreateMockFilter(false) + }, + 0 + }; + + yield return new object[] +{ + "Only the last filter is applicable", + new List>{ + CreateMockFilter(false), + CreateMockFilter(false), + CreateMockFilter(true) + }, + 2 + }; + + yield return new object[] + { + "Only one filter in the middle is applicable", + new List>{ + CreateMockFilter(false), + CreateMockFilter(true), + CreateMockFilter(false) + }, + 1 + }; + + yield return new object[] + { + "The first applicable filter masks the other applicable filters", + new List>{ + CreateMockFilter(false), + CreateMockFilter(true), + CreateMockFilter(true) + }, + 1 + }; + + yield return new object[] + { + "If all filters are applicable, only the first one is called", + new List>{ + CreateMockFilter(true), + CreateMockFilter(true), + CreateMockFilter(true) + }, + 0 + }; + } + + private static Mock CreateMockFilter(bool applicable) + { + var mockFilter = new Mock(); + + mockFilter.Setup( + mock => mock.Apply( + It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(new List()); + + mockFilter.Setup(mock => mock.IsApplicable(It.IsAny())).Returns(applicable); + + return mockFilter; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/UsePredefinedOperationIdFilterTest.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/UsePredefinedOperationIdFilterTest.cs new file mode 100644 index 0000000..a29f2e8 --- /dev/null +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/UsePredefinedOperationIdFilterTest.cs @@ -0,0 +1,389 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using FluentAssertions; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Exceptions; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.PreprocessingOperationFilters; +using Microsoft.OpenApi.Models; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.FilterTests +{ + [Collection("DefaultSettings")] + public class UsePredefinedOperationIdFilterTest + { + private readonly ITestOutputHelper _output; + + public UsePredefinedOperationIdFilterTest(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + [MemberData(nameof(GetTestCasesForIsApplicableShouldReferToTheHasOperationIdPredicate))] + public void IsApplicableShouldReferToTheHasOperationIdPredicate( + string testName, + bool shouldBeApplicable) + { + _output.WriteLine(testName); + + // Prepare + XElement calledElement = null; + bool mockHasOperationIdFunc(XElement e) + { + calledElement = e; + return shouldBeApplicable; + } + + var filter = new UsePredefinedOperationIdFilter( + DefaultGetUrlFunc, + DefaultGetOperationMethodFunc, + DefaultGetOperationIdFunc, + mockHasOperationIdFunc); + + var element = XElement.Parse("Not used in this test due of the mocked function."); + + // Action + var result = filter.IsApplicable(element); + + // Assert + result.Should().Be(shouldBeApplicable); + } + + [Fact] + public void ApplyShouldCreateTheExpectedOpenApiPath() + { + // Prepare + var expectedUrl = "/expected/operation/path"; + var expectedOperationType = OperationType.Post; + var expectedOperationId = "Expected_Operation_Id"; + + string MockGetUrlFunc(XElement e) + { + return (string)expectedUrl.Clone(); + } + + OperationType MockGetOperationTypeFunc(XElement e) + { + return expectedOperationType; + } + + string MockGetOperationIdFunc(XElement e) + { + return (string)expectedOperationId.Clone(); + } + + + var filter = new UsePredefinedOperationIdFilter( + MockGetUrlFunc, + MockGetOperationTypeFunc, + MockGetOperationIdFunc, + DefaultHasOperationIdFunc); + + var element = XElement.Parse("Not used in this test due of the mocked functions."); + + // Action + var openApiPaths = new OpenApiPaths(); + var result = filter.Apply(openApiPaths, element, new PreProcessingOperationFilterSettings()); + + // Assert + result.Should().BeEmpty(); + + openApiPaths.Keys.Count.Should().Be(1); + openApiPaths.Should().ContainKey(expectedUrl); + + openApiPaths[expectedUrl].Operations.Keys.Count.Should().Be(1); + openApiPaths[expectedUrl].Operations.Should().ContainKey(expectedOperationType); + + openApiPaths[expectedUrl].Operations[expectedOperationType] + .OperationId.Should().BeEquivalentTo(expectedOperationId); + } + + [Fact] + public void ApplyShouldReturnTheExpectedErrorIfGetUrlThrowsException() + { + // Prepare + string MockGetUlrFunc(XElement e) + { + throw new InvalidUrlException(); + } + + var filter = new UsePredefinedOperationIdFilter( + MockGetUlrFunc, + DefaultGetOperationMethodFunc, + DefaultGetOperationIdFunc, + DefaultHasOperationIdFunc); + + var element = XElement.Parse("Not used in this test due of the mocked functions."); + + // Action + var openApiPaths = new OpenApiPaths(); + var result = filter.Apply(openApiPaths, element, new PreProcessingOperationFilterSettings()); + + // Assert + result.Count.Should().Be(1); + result[0].ExceptionType.Should().Be("InvalidUrlException"); + openApiPaths.Keys.Count.Should().Be(0); + } + + [Fact] + public void ApplyShouldReturnTheExpectedErrorIfGetOperationMethodThrowsException() + { + // Prepare + OperationType MockGetOperationMethodFunc(XElement e) + { + throw new InvalidVerbException(); + } + + var filter = new UsePredefinedOperationIdFilter( + DefaultGetUrlFunc, + MockGetOperationMethodFunc, + DefaultGetOperationIdFunc, + DefaultHasOperationIdFunc); + + var element = XElement.Parse("Not used in this test due of the mocked functions."); + + // Action + var openApiPaths = new OpenApiPaths(); + var result = filter.Apply(openApiPaths, element, new PreProcessingOperationFilterSettings()); + + // Assert + result.Count.Should().Be(1); + result[0].ExceptionType.Should().Be("InvalidVerbException"); + openApiPaths.Keys.Count.Should().Be(0); + } + + [Fact] + public void ApplyShouldReturnTheExpectedErrorIfGetOperationIdThrowsException() + { + // Prepare + string MockGetOperationIdFunc(XElement e) + { + throw new InvalidOperationIdException(); + } + + var filter = new UsePredefinedOperationIdFilter( + DefaultGetUrlFunc, + DefaultGetOperationMethodFunc, + MockGetOperationIdFunc, + DefaultHasOperationIdFunc); + + var element = XElement.Parse("Not used in this test due of the mocked functions."); + + // Action + var openApiPaths = new OpenApiPaths(); + var result = filter.Apply(openApiPaths, element, new PreProcessingOperationFilterSettings()); + + // Assert + result.Count.Should().Be(1); + result[0].ExceptionType.Should().Be("InvalidOperationIdException"); + openApiPaths.Keys.Count.Should().Be(0); + } + + public static IEnumerable GetTestCasesForIsApplicableShouldReferToTheHasOperationIdPredicate() + { + yield return new object[] + { + "Applicable is HasOperationId returns true", + true + }; + + yield return new object[] + { + "Not applicable is HasOperationId returns false", + false + }; + } + + //[Fact] + //public void ApplyShouldUseTheOperationIdXmlTagAsOperationId() + //{ + // // Prepare + // var element = XElement.Parse(@" + // + // Assign Role + // Role + // https://localhost/v2/role/{role}/assign + // put + // AccessControl_AssignRole + // + // "); + + // var openApiPaths = new OpenApiPaths(); + // var settings = new PreProcessingOperationFilterSettings(); + + // var filter = new UsePredefinedOperationIdFilter(); + + // // Action + // filter.Apply(openApiPaths, element, settings); + + // // Assert + // string url = "/v2/role/{role}/assign"; + // OperationType operationType = OperationType.Put; + // var expectedOperationId = "AccessControl_AssignRole"; + + // openApiPaths.Should().ContainKey(url); + // openApiPaths[url].Operations.Should().ContainKey(operationType); + + // openApiPaths[url].Operations[operationType] + // .OperationId.Should().BeEquivalentTo(expectedOperationId); + //} + + //[Theory] + //[MemberData(nameof(GetTestCasesForApplyShouldUseVerbXmlTagAsOperationType))] + //public void ApplyShouldUseVerbXmlTagAsOperationType( + // string testName, + // XElement element, + // string url, + // OperationType expectedOperationType) + //{ + // _output.WriteLine(testName); + + // // Prepare + // var openApiPaths = new OpenApiPaths(); + // var settings = new PreProcessingOperationFilterSettings(); + + // var filter = new UsePredefinedOperationIdFilter(); + + // // Action + // filter.Apply(openApiPaths, element, settings); + + // // Assert + // openApiPaths.Should().ContainKey(url); + // openApiPaths[url].Operations.Should().ContainKey(expectedOperationType); + //} + + //public static IEnumerable GetTestCasesForApplyShouldUseVerbXmlTagAsOperationType() + //{ + // var operationTypeStringList = new string[] + // { + // "get", "put", "post", "delete", "options", "head", "patch", "trace" + // }; + + // var expectedOperationTypeList = new OperationType[] + // { + // OperationType.Get, OperationType.Put, OperationType.Post, OperationType.Delete, + // OperationType.Options, OperationType.Head, OperationType.Patch, OperationType.Trace + // }; + + // foreach (var value in operationTypeStringList.Zip(expectedOperationTypeList)) + // { + // var element = XElement.Parse($@" + // + // Assign Role + // Role + // https://localhost/v2/role/{{role}}/assign + // put + // AccessControl_AssignRole + // + // "); + // } + + // yield return new object[] + // { + // "Not applicable if no operationId XML tag is present", + // XElement.Parse(@" + // + // Assign Role + // Role + // + // "), + // false + // }; + + // yield return new object[] + // { + // "Applicable if one operationId XML tag is present", + // XElement.Parse(@" + // + // Assign Role + // Role + // AccessControl_AssignRole + // + // "), + // true + // }; + + // yield return new object[] + // { + // "Applicable if multiple operationId XML tags are present", + // XElement.Parse(@" + // + // AssignRole + // Assign Role + // Role + // AccessControl_AssignRole + // + // "), + // true + // }; + //} + public static IEnumerable GetTestCasesForApplyShouldUseTheOperationIdXmlTagAsOperationId() + { + yield return new object[] + { + "", + XElement.Parse(@" + + Assign Role + Role + + "), + false + }; + + yield return new object[] + { + "Applicable if one operationId XML tag is present", + XElement.Parse(@" + + Assign Role + Role + AccessControl_AssignRole + + "), + true + }; + + yield return new object[] + { + "Applicable if multiple operationId XML tags are present", + XElement.Parse(@" + + AssignRole + Assign Role + Role + AccessControl_AssignRole + + "), + true + }; + } + + private static string DefaultGetUrlFunc(XElement e) + { + return "/default/url"; + } + + private static OperationType DefaultGetOperationMethodFunc(XElement e) + { + return OperationType.Post; + } + + private static string DefaultGetOperationIdFunc(XElement e) + { + return "Default_Operation_Id"; + } + + private static bool DefaultHasOperationIdFunc(XElement e) + { + return true; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.csproj b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.csproj index d8d66db..3cbc957 100644 --- a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.csproj +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.csproj @@ -20,6 +20,7 @@ + @@ -44,6 +45,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Input/AnnotationPredefinedAndGeneratedOperationId.xml b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Input/AnnotationPredefinedAndGeneratedOperationId.xml new file mode 100644 index 0000000..ba9a014 --- /dev/null +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Input/AnnotationPredefinedAndGeneratedOperationId.xml @@ -0,0 +1,249 @@ + + + + Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.SampleApis + + + + + Responsible for route configuration + + + + + Registers routes + + Route collection + + + + Web config. + + + + + Register the configuration, services, and routes. + + HTTP Configuration + + + + Define V1 operations. + + + + + Sample Get 1 + + Sample V1 + GET + SampleControllerV1_SampleGet1 + http://localhost:9000/V1/samples/{id}?queryBool={queryBool} + Header param 1 + + + + where T is + where T1 is + where T2 is Header param 2 + + + Header param 3 + + The object id + Sample query boolean + + > +
Test Header
+ > + Sample object retrieved +
+ + Bad request + + Group1 + Group2 + The sample object 1 +
+ + + Sample Get 2 + + Sample V1 + GET + http://localhost:9000/V1/samples + Header param 1 + Header param 2 + Header param 3 + + Paged Entity contract + + + Bad request + + The sample object 3 + + + + Sample Post + + Sample V1 + POST + http://localhost:9000/V1/samples + Header param 1 + Header param 2 + Header param 3 + + Sample object + + + Sample object posted + + + Bad request + + + + + Sample put + + Sample V1 + PUT + SampleControllerV1_SamplePut + http://localhost:9000/V1/samples/{id} + Header param 1 + Header param 2 + Header param 3 + The object id + + Sample object + + + The sample object updated + + + Bad request + + The sample object 1 + + + + Defines V3 operations. + + + + + Sample get 1 + + Sample V3 + GET + http://localhost:9000/V3/samples/ + Header param 1 + Header param 2 + Header param 3 + + + where T is + where T1 is + where T2 is + List of sample objects. + + + Bad request + + + + + Sample get 2 + + Sample V3 + GET + SampleControllerV3_SampleGet2 + http://localhost:9000/V3/samples/{id}?queryString={queryString} + Header param 1 + Header param 2 + Header param 3 + The object id + The sample query string + + + where T1 is + where T2 is + List of sample objects. + + + Bad request + + + + + Defines V2 operations. + + + + + Sample delete + + Sample V2 + DELETE + SampleControllerV2_DeleteEntity + http://localhost:9000/V2/samples/{id} + Header param 1 + Header param 2 + Header param 3 + The object id + + Sample object deleted + + + Bad request + + + + + Sample get 1 + + Sample V2 + GET + http://localhost:9000/V2/samples/ + Header param 1 + Header param 2 + Header param 3 + + where T is List of sample objects + + + Bad request + + + + + Sample get 2 + + Sample V2 + GET + http://localhost:9000/V2/samples/{id}?queryString={queryString} + Header param 1 + Header param 2 + Header param 3 + The object id + The sample query string + + Sample object retrieved + + + Bad request + + + + + Web API Application. + + + + + Start application. + + +
+
diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Input/AnnotationPredefinedOperationId.xml b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Input/AnnotationPredefinedOperationId.xml new file mode 100644 index 0000000..7de4410 --- /dev/null +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Input/AnnotationPredefinedOperationId.xml @@ -0,0 +1,254 @@ + + + + Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.SampleApis + + + + + Responsible for route configuration + + + + + Registers routes + + Route collection + + + + Web config. + + + + + Register the configuration, services, and routes. + + HTTP Configuration + + + + Define V1 operations. + + + + + Sample Get 1 + + Sample V1 + GET + SampleControllerV1_SampleGet1 + http://localhost:9000/V1/samples/{id}?queryBool={queryBool} + Header param 1 + + + + where T is + where T1 is + where T2 is Header param 2 + + + Header param 3 + + The object id + Sample query boolean + + > +
Test Header
+ > + Sample object retrieved +
+ + Bad request + + Group1 + Group2 + The sample object 1 +
+ + + Sample Get 2 + + Sample V1 + GET + SampleControllerV1_SampleGet2 + http://localhost:9000/V1/samples + Header param 1 + Header param 2 + Header param 3 + + Paged Entity contract + + + Bad request + + The sample object 3 + + + + Sample Post + + Sample V1 + POST + SampleControllerV1_SamplePost + http://localhost:9000/V1/samples + Header param 1 + Header param 2 + Header param 3 + + Sample object + + + Sample object posted + + + Bad request + + + + + Sample put + + Sample V1 + PUT + SampleControllerV1_SamplePut + http://localhost:9000/V1/samples/{id} + Header param 1 + Header param 2 + Header param 3 + The object id + + Sample object + + + The sample object updated + + + Bad request + + The sample object 1 + + + + Defines V3 operations. + + + + + Sample get 1 + + Sample V3 + GET + SampleControllerV3_SampleGet1 + http://localhost:9000/V3/samples/ + Header param 1 + Header param 2 + Header param 3 + + + where T is + where T1 is + where T2 is + List of sample objects. + + + Bad request + + + + + Sample get 2 + + Sample V3 + GET + SampleControllerV3_SampleGet2 + http://localhost:9000/V3/samples/{id}?queryString={queryString} + Header param 1 + Header param 2 + Header param 3 + The object id + The sample query string + + + where T1 is + where T2 is + List of sample objects. + + + Bad request + + + + + Defines V2 operations. + + + + + Sample delete + + Sample V2 + DELETE + SampleControllerV2_DeleteEntity + http://localhost:9000/V2/samples/{id} + Header param 1 + Header param 2 + Header param 3 + The object id + + Sample object deleted + + + Bad request + + + + + Sample get 1 + + Sample V2 + GET + SampleControllerV2_SampleGet1 + http://localhost:9000/V2/samples/ + Header param 1 + Header param 2 + Header param 3 + + where T is List of sample objects + + + Bad request + + + + + Sample get 2 + + Sample V2 + GET + SampleControllerV2_SampleGet2 + http://localhost:9000/V2/samples/{id}?queryString={queryString} + Header param 1 + Header param 2 + Header param 3 + The object id + The sample query string + + Sample object retrieved + + + Bad request + + + + + Web API Application. + + + + + Start application. + + +
+
diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/OpenApiDocumentGeneratorTest.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/OpenApiDocumentGeneratorTest.cs index f352eaf..2fcebc8 100644 --- a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/OpenApiDocumentGeneratorTest.cs +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/OpenApiDocumentGeneratorTest.cs @@ -1094,6 +1094,56 @@ public static IEnumerable GetTestCasesForValidDocumentationShouldRetur OutputDirectory, "AnnotationWithRuntimeSerialization.Json") }; + + yield return new object[] + { + "All operations have predefined operation Id", + new List + { + Path.Combine(InputDirectory, "AnnotationPredefinedOperationId.xml"), + Path.Combine(InputDirectory, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.xml") + }, + new List + { + Path.Combine( + InputDirectory, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.SampleApis.dll"), + Path.Combine( + InputDirectory, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.dll") + }, + "1.0.0", + 9, + Path.Combine( + OutputDirectory, + "AnnotationPredefinedOperationId.Json") + }; + + yield return new object[] + { + "A few operations have predefined operation Id, the others are generated", + new List + { + Path.Combine(InputDirectory, "AnnotationPredefinedAndGeneratedOperationId.xml"), + Path.Combine(InputDirectory, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.xml") + }, + new List + { + Path.Combine( + InputDirectory, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.SampleApis.dll"), + Path.Combine( + InputDirectory, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.dll") + }, + "1.0.0", + 9, + Path.Combine( + OutputDirectory, + "AnnotationPredefinedAndGeneratedOperationId.Json") + }; } [Theory] diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Output/AnnotationPredefinedAndGeneratedOperationId.Json b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Output/AnnotationPredefinedAndGeneratedOperationId.Json new file mode 100644 index 0000000..5811f81 --- /dev/null +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Output/AnnotationPredefinedAndGeneratedOperationId.Json @@ -0,0 +1,758 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.SampleApis", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:9000" + } + ], + "paths": { + "/V1/samples/{id}": { + "get": { + "tags": [ + "Sample V1" + ], + "summary": "Sample Get 1", + "operationId": "SampleControllerV1_SampleGet1", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.ISampleObject4_Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1-Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2_" + } + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "queryBool", + "in": "query", + "description": "Sample query boolean", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Sample object retrieved", + "headers": { + "TestHeader": { + "description": "Test Header", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "put": { + "tags": [ + "Sample V1" + ], + "summary": "Sample put", + "operationId": "SampleControllerV1_SamplePut", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Sample object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "The sample object updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V1/samples": { + "get": { + "tags": [ + "Sample V1" + ], + "summary": "Sample Get 2", + "operationId": "getV1Samples", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Paged Entity contract", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject3" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "post": { + "tags": [ + "Sample V1" + ], + "summary": "Sample Post", + "operationId": "postV1Samples", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Sample object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject3" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Sample object posted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject3" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V3/samples/": { + "get": { + "tags": [ + "Sample V3" + ], + "summary": "Sample get 1", + "operationId": "getV3Samples", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of sample objects.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.ISampleObject4_Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1-Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2_" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V3/samples/{id}": { + "get": { + "tags": [ + "Sample V3" + ], + "summary": "Sample get 2", + "operationId": "SampleControllerV3_SampleGet2", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "queryString", + "in": "query", + "description": "The sample query string", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of sample objects.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.ISampleObject4_Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1-Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2_" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V2/samples/{id}": { + "delete": { + "tags": [ + "Sample V2" + ], + "summary": "Sample delete", + "operationId": "SampleControllerV2_DeleteEntity", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Sample object deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "get": { + "tags": [ + "Sample V2" + ], + "summary": "Sample get 2", + "operationId": "getV2SamplesById", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "queryString", + "in": "query", + "description": "The sample query string", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Sample object retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V2/samples/": { + "get": { + "tags": [ + "Sample V2" + ], + "summary": "Sample get 1", + "operationId": "getV2Samples", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of sample objects", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1": { + "required": [ + "samplePropertyString3", + "samplePropertyString4", + "samplePropertyEnum" + ], + "type": "object", + "properties": { + "samplePropertyBool": { + "type": "boolean", + "description": "Gets or sets the sample property bool" + }, + "samplePropertyStringInt": { + "type": "integer", + "description": "Gets or sets the sample property int", + "format": "int32" + }, + "samplePropertyString1": { + "type": "string", + "description": "Gets or sets the sample property string 1" + }, + "samplePropertyString2": { + "type": "string", + "description": "Gets or sets the sample property string 2" + }, + "samplePropertyString3": { + "type": "string", + "description": "Gets or sets the sample property string 3" + }, + "samplePropertyString4": { + "type": "string", + "description": "Gets or sets the sample property string 4" + }, + "samplePropertyEnum": { + "enum": [ + "SampleEnumValue1", + "SampleEnumValue2" + ], + "type": "string", + "description": "Gets or sets the sample property enum" + } + } + }, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.ISampleObject4_Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1-Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2_": { + "required": [ + "samplePropertyTypeT1", + "samplePropertyTypeT2" + ], + "type": "object", + "properties": { + "samplePropertyTypeT1": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + }, + "samplePropertyTypeT2": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2" + } + } + }, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2": { + "required": [ + "samplePropertyInt", + "samplePropertyObjectDictionary", + "samplePropertyObjectList" + ], + "type": "object", + "properties": { + "samplePropertyEnum": { + "enum": [ + "SampleEnumValue1", + "SampleEnumValue2" + ], + "type": "string", + "description": "Gets or sets the sample property enum" + }, + "samplePropertyInt": { + "type": "string", + "description": "Gets or sets the sample property string 1" + }, + "samplePropertyObjectDictionary": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + }, + "description": "Gets the sample property object dictionary" + }, + "samplePropertyObjectList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + }, + "description": "Gets the sample property object list" + }, + "samplePropertyString1": { + "type": "string", + "description": "Gets or sets the sample property string 1" + } + } + }, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject3": { + "required": [ + "samplePropertyObject" + ], + "type": "object", + "properties": { + "samplePropertyObject": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2" + }, + "samplePropertyObjectList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + }, + "description": "Gets the sample property object list" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Output/AnnotationPredefinedOperationId.Json b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Output/AnnotationPredefinedOperationId.Json new file mode 100644 index 0000000..6f5444a --- /dev/null +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OpenApiDocumentGeneratorTests/Output/AnnotationPredefinedOperationId.Json @@ -0,0 +1,758 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.SampleApis", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:9000" + } + ], + "paths": { + "/V1/samples/{id}": { + "get": { + "tags": [ + "Sample V1" + ], + "summary": "Sample Get 1", + "operationId": "SampleControllerV1_SampleGet1", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.ISampleObject4_Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1-Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2_" + } + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "queryBool", + "in": "query", + "description": "Sample query boolean", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Sample object retrieved", + "headers": { + "TestHeader": { + "description": "Test Header", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "put": { + "tags": [ + "Sample V1" + ], + "summary": "Sample put", + "operationId": "SampleControllerV1_SamplePut", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Sample object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "The sample object updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V1/samples": { + "get": { + "tags": [ + "Sample V1" + ], + "summary": "Sample Get 2", + "operationId": "SampleControllerV1_SampleGet2", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Paged Entity contract", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject3" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "post": { + "tags": [ + "Sample V1" + ], + "summary": "Sample Post", + "operationId": "SampleControllerV1_SamplePost", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Sample object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject3" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Sample object posted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject3" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V3/samples/": { + "get": { + "tags": [ + "Sample V3" + ], + "summary": "Sample get 1", + "operationId": "SampleControllerV3_SampleGet1", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of sample objects.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.ISampleObject4_Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1-Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2_" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V3/samples/{id}": { + "get": { + "tags": [ + "Sample V3" + ], + "summary": "Sample get 2", + "operationId": "SampleControllerV3_SampleGet2", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "queryString", + "in": "query", + "description": "The sample query string", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of sample objects.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.ISampleObject4_Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1-Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2_" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V2/samples/{id}": { + "delete": { + "tags": [ + "Sample V2" + ], + "summary": "Sample delete", + "operationId": "SampleControllerV2_DeleteEntity", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Sample object deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "get": { + "tags": [ + "Sample V2" + ], + "summary": "Sample get 2", + "operationId": "SampleControllerV2_SampleGet2", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "in": "path", + "description": "The object id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "queryString", + "in": "query", + "description": "The sample query string", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Sample object retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/V2/samples/": { + "get": { + "tags": [ + "Sample V2" + ], + "summary": "Sample get 1", + "operationId": "SampleControllerV2_SampleGet1", + "parameters": [ + { + "name": "sampleHeaderParam1", + "in": "header", + "description": "Header param 1", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sampleHeaderParam2", + "in": "header", + "description": "Header param 2", + "schema": { + "type": "string" + } + }, + { + "name": "sampleHeaderParam3", + "in": "header", + "description": "Header param 3", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "List of sample objects", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1": { + "required": [ + "samplePropertyString3", + "samplePropertyString4", + "samplePropertyEnum" + ], + "type": "object", + "properties": { + "samplePropertyBool": { + "type": "boolean", + "description": "Gets or sets the sample property bool" + }, + "samplePropertyStringInt": { + "type": "integer", + "description": "Gets or sets the sample property int", + "format": "int32" + }, + "samplePropertyString1": { + "type": "string", + "description": "Gets or sets the sample property string 1" + }, + "samplePropertyString2": { + "type": "string", + "description": "Gets or sets the sample property string 2" + }, + "samplePropertyString3": { + "type": "string", + "description": "Gets or sets the sample property string 3" + }, + "samplePropertyString4": { + "type": "string", + "description": "Gets or sets the sample property string 4" + }, + "samplePropertyEnum": { + "enum": [ + "SampleEnumValue1", + "SampleEnumValue2" + ], + "type": "string", + "description": "Gets or sets the sample property enum" + } + } + }, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.ISampleObject4_Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1-Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2_": { + "required": [ + "samplePropertyTypeT1", + "samplePropertyTypeT2" + ], + "type": "object", + "properties": { + "samplePropertyTypeT1": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + }, + "samplePropertyTypeT2": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2" + } + } + }, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2": { + "required": [ + "samplePropertyInt", + "samplePropertyObjectDictionary", + "samplePropertyObjectList" + ], + "type": "object", + "properties": { + "samplePropertyEnum": { + "enum": [ + "SampleEnumValue1", + "SampleEnumValue2" + ], + "type": "string", + "description": "Gets or sets the sample property enum" + }, + "samplePropertyInt": { + "type": "string", + "description": "Gets or sets the sample property string 1" + }, + "samplePropertyObjectDictionary": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + }, + "description": "Gets the sample property object dictionary" + }, + "samplePropertyObjectList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + }, + "description": "Gets the sample property object list" + }, + "samplePropertyString1": { + "type": "string", + "description": "Gets or sets the sample property string 1" + } + } + }, + "Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject3": { + "required": [ + "samplePropertyObject" + ], + "type": "object", + "properties": { + "samplePropertyObject": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject2" + }, + "samplePropertyObjectList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.Contracts.SampleObject1" + }, + "description": "Gets the sample property object list" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_GetUrlTests.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_GetUrlTests.cs new file mode 100644 index 0000000..c7dfac7 --- /dev/null +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_GetUrlTests.cs @@ -0,0 +1,113 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Xml.Linq; +using FluentAssertions; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Exceptions; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.FilterTests +{ + [Collection("DefaultSettings")] + public class OperationHandler_GetUrlTests + { + private readonly ITestOutputHelper _output; + + public OperationHandler_GetUrlTests(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + [MemberData(nameof(GetTestCasesForGetUrlShouldReturnTheAbsolutePathUsingUrlXmlTag))] + public void GetUrlShouldReturnTheAbsolutePathUsingUrlXmlTag( + string testName, + XElement element, + string expectedUrl) + { + _output.WriteLine(testName); + + // Action + var url = OperationHandler.GetUrl(element); + + // Assert + url.Should().BeEquivalentTo(expectedUrl); + } + + [Fact] + public void GetUrlShouldThrowInvalidUrlExceptionByMissingUrlTag() + { + var element = XElement.Parse($@" + + + + "); + + // Action + Action action = () => { OperationHandler.GetUrl(element); }; + + // Assert + action.Should().ThrowExactly(); + } + + + [Fact] + public void GetUrlShouldThrowInvalidUrlExceptionByWrongUrl() + { + var element = XElement.Parse($@" + + This is not a valid url. + + "); + + // Action + Action action = () => { OperationHandler.GetUrl(element); }; + + // Assert + action.Should().ThrowExactly(); + } + + public static IEnumerable GetTestCasesForGetUrlShouldReturnTheAbsolutePathUsingUrlXmlTag() + { + yield return new object[] + { + "Simple url works", + XElement.Parse(@" + + https://localhost/v2/role/{role}/assign + + "), + "/v2/role/{role}/assign" + }; + + yield return new object[] + { + "Complex encoded url works", + XElement.Parse($@" + + {WebUtility.UrlEncode("https://localhost/v2/role/{role}/assign?p1=1&p2=2")} + + "), + "/v2/role/{role}/assign" + }; + + yield return new object[] + { + "The first url is considerd", + XElement.Parse(@" + + https://localhost/v2/role/{role}/assign + https://localhost/this/url/should/not/be/considered + + "), + "/v2/role/{role}/assign" + }; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationIdTests.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationIdTests.cs new file mode 100644 index 0000000..4859a17 --- /dev/null +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationIdTests.cs @@ -0,0 +1,146 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using FluentAssertions; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Exceptions; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.FilterTests +{ + [Collection("DefaultSettings")] + public class OperationHandler_OperationIdTests + { + private readonly ITestOutputHelper _output; + + public OperationHandler_OperationIdTests(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + [MemberData(nameof(GetTestCasesForHasOperationIdShouldReferToTheOperationIdXmlTag))] + public void HasOperationIdShouldReferToTheOperationIdXmlTag( + string testName, + XElement element, + bool expectedResult) + { + _output.WriteLine(testName); + + // Action + var result = OperationHandler.HasOperationId(element); + + // Assert + result.Should().Be(expectedResult); + } + + [Fact] + public void HasOperationIdShouldReturnFalseByNullInput() + { + // Action + var result = OperationHandler.HasOperationId(null); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public void GetOperationIdShouldUseTheOperationIdXmlTagAsOperationId() + { + // Prepare + var element = XElement.Parse(@" + + AccessControl_AssignRole + + "); + + // Action + var result = OperationHandler.GetOperationId(element); + + // Assert + var expectedOperationId = "AccessControl_AssignRole"; + result.Should().BeEquivalentTo(expectedOperationId); + } + + [Theory] + [MemberData(nameof(GetTestCasesForGetOperationIdShouldThrowInvalidOperationIdExceptionByWrongXml))] + public void GetOperationIdShouldThrowInvalidOperationIdExceptionByWrongXml( + string testName, + XElement element) + { + _output.WriteLine(testName); + + // Action + Action action = () => { OperationHandler.GetOperationId(element); }; + + // Assert + action.Should().ThrowExactly(); + } + + public static IEnumerable GetTestCasesForHasOperationIdShouldReferToTheOperationIdXmlTag() + { + yield return new object[] + { + "Return false if no operationId XML tag is present", + XElement.Parse(@" + + + + "), + false + }; + + yield return new object[] + { + "Return true if one operationId XML tag is present", + XElement.Parse(@" + + AccessControl_AssignRole + + "), + true + }; + + yield return new object[] + { + "Return true if multiple operationId XML tags are present", + XElement.Parse(@" + + AssignRole + AccessControl_AssignRole + + "), + true + }; + } + + public static IEnumerable GetTestCasesForGetOperationIdShouldThrowInvalidOperationIdExceptionByWrongXml() + { + yield return new object[] + { + "Missing operationId tag", + XElement.Parse(@" + + + + ") + }; + + yield return new object[] + { + "More than one operationId tag", + XElement.Parse(@" + + First operation id + Second operation id + + ") + }; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationMethodTests.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationMethodTests.cs new file mode 100644 index 0000000..4d7244c --- /dev/null +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationMethodTests.cs @@ -0,0 +1,91 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See License.txt in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using FluentAssertions; +using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Exceptions; +using Microsoft.OpenApi.Models; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.FilterTests +{ + [Collection("DefaultSettings")] + public class OperationHandler_OperationMethodTests + { + private readonly ITestOutputHelper _output; + + public OperationHandler_OperationMethodTests(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + [MemberData(nameof(GetTestCasesForGetOperationMethodShouldUseTheVerbXmlTagAsOperationType))] + public void GetOperationMethodShouldUseTheVerbXmlTagAsOperationType( + string testName, + XElement element, + OperationType expectedOperationType) + { + _output.WriteLine(testName); + + // Action + var result = OperationHandler.GetOperationMethod(element); + + // Assert + result.Should().BeEquivalentTo(expectedOperationType); + } + + [Fact] + public void GetOperationMethodShouldThrowInvalidVerbExceptionByMissingVerbTag() + { + var element = XElement.Parse($@" + + + + "); + + // Action + Action action = () => { OperationHandler.GetOperationMethod(element); }; + + // Assert + action.Should().ThrowExactly(); + } + + public static IEnumerable GetTestCasesForGetOperationMethodShouldUseTheVerbXmlTagAsOperationType() + { + var operationTypeStringList = new string[] + { + "get", "put", "post", "delete", "options", "head", "patch", "trace" + }; + + var expectedOperationTypeList = new OperationType[] + { + OperationType.Get, OperationType.Put, OperationType.Post, OperationType.Delete, + OperationType.Options, OperationType.Head, OperationType.Patch, OperationType.Trace + }; + + var operationTypeList = operationTypeStringList.Zip(expectedOperationTypeList, (s, t) => new { Input = s, Exp = t }); + foreach (var item in operationTypeList) + { + var element = XElement.Parse($@" + + {item.Input} + + "); + + yield return new object[] + { + "Test for " + item.Input, + element, + item.Exp + }; + } + } + } +} \ No newline at end of file From e8c24126d074993bc2e8d6d052739bfbd4e06700 Mon Sep 17 00:00:00 2001 From: Tibor Takacs Date: Thu, 9 Jul 2020 16:09:05 +0100 Subject: [PATCH 2/4] Minor fixes and improvements. --- .../Extensions/StringExtensions.cs | 10 - .../CreateOperationMetaFilter.cs | 2 +- .../UsePredefinedOperationIdFilter.cs | 5 +- .../CreateOperationMetaFilterTest.cs | 2 +- .../UsePredefinedOperationIdFilterTest.cs | 182 +++--------------- 5 files changed, 30 insertions(+), 171 deletions(-) diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Extensions/StringExtensions.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Extensions/StringExtensions.cs index 6a43501..e5196de 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Extensions/StringExtensions.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/Extensions/StringExtensions.cs @@ -245,15 +245,5 @@ public static string ToTitleCase(this string value) return value.Substring(startIndex: 0, length: 1).ToUpperInvariant() + value.Substring(startIndex: 1); } - - /// - /// Extracts the absolute path from a full URL string. - /// - /// The string in URL format. - /// The absolute path inside the URL. - public static string UrlStringToAbsolutePath(this string value) - { - return WebUtility.UrlDecode(new Uri(value).AbsolutePath); - } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs index fb86148..57b9a9e 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs @@ -49,7 +49,7 @@ internal CreateOperationMetaFilter( /// /// Initializes the OpenApi operation based on the annotation XML element. - /// + /// It applies the first applicable filter to create operations. /// /// The paths to be updated. /// The xml element representing an operation in the annotation xml. diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs index 41b1775..bc2b973 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs @@ -64,9 +64,8 @@ public bool IsApplicable(XElement element) } /// - /// Creates the operation in the Paths object using the operationId tag of the operation. - /// It does not consider optional parameters, it create one operation for the full path. - /// the URL value and creates multiple operations based on optional parameters. + /// Creates the operation using the operationId tag of the operation. It does not consider optional + /// parameters, it creates one operation for the full path. /// /// The paths to be updated. /// The xml element representing an operation in the annotation xml. diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/CreateOperationMetaFilterTest.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/CreateOperationMetaFilterTest.cs index cb90d2c..25ca7be 100644 --- a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/CreateOperationMetaFilterTest.cs +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/CreateOperationMetaFilterTest.cs @@ -118,7 +118,7 @@ public void ApplyShouldNotReturnErrorIfTheAppliedFilterDoesNotReturnError() } [Fact] - public void ApplyShouldReturnTheErrorListOFTheAppliedFilter() + public void ApplyShouldReturnTheErrorListOfTheAppliedFilter() { // Prepare var openApiPaths = new OpenApiPaths(); diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/UsePredefinedOperationIdFilterTest.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/UsePredefinedOperationIdFilterTest.cs index a29f2e8..5f3faa3 100644 --- a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/UsePredefinedOperationIdFilterTest.cs +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/FilterTests/UsePredefinedOperationIdFilterTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Xml.Linq; using FluentAssertions; using Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Exceptions; @@ -201,169 +202,38 @@ public static IEnumerable GetTestCasesForIsApplicableShouldReferToTheH }; } - //[Fact] - //public void ApplyShouldUseTheOperationIdXmlTagAsOperationId() - //{ - // // Prepare - // var element = XElement.Parse(@" - // - // Assign Role - // Role - // https://localhost/v2/role/{role}/assign - // put - // AccessControl_AssignRole - // - // "); - - // var openApiPaths = new OpenApiPaths(); - // var settings = new PreProcessingOperationFilterSettings(); - - // var filter = new UsePredefinedOperationIdFilter(); - - // // Action - // filter.Apply(openApiPaths, element, settings); - - // // Assert - // string url = "/v2/role/{role}/assign"; - // OperationType operationType = OperationType.Put; - // var expectedOperationId = "AccessControl_AssignRole"; - - // openApiPaths.Should().ContainKey(url); - // openApiPaths[url].Operations.Should().ContainKey(operationType); - - // openApiPaths[url].Operations[operationType] - // .OperationId.Should().BeEquivalentTo(expectedOperationId); - //} - - //[Theory] - //[MemberData(nameof(GetTestCasesForApplyShouldUseVerbXmlTagAsOperationType))] - //public void ApplyShouldUseVerbXmlTagAsOperationType( - // string testName, - // XElement element, - // string url, - // OperationType expectedOperationType) - //{ - // _output.WriteLine(testName); - - // // Prepare - // var openApiPaths = new OpenApiPaths(); - // var settings = new PreProcessingOperationFilterSettings(); - - // var filter = new UsePredefinedOperationIdFilter(); - - // // Action - // filter.Apply(openApiPaths, element, settings); - - // // Assert - // openApiPaths.Should().ContainKey(url); - // openApiPaths[url].Operations.Should().ContainKey(expectedOperationType); - //} - - //public static IEnumerable GetTestCasesForApplyShouldUseVerbXmlTagAsOperationType() - //{ - // var operationTypeStringList = new string[] - // { - // "get", "put", "post", "delete", "options", "head", "patch", "trace" - // }; - - // var expectedOperationTypeList = new OperationType[] - // { - // OperationType.Get, OperationType.Put, OperationType.Post, OperationType.Delete, - // OperationType.Options, OperationType.Head, OperationType.Patch, OperationType.Trace - // }; - - // foreach (var value in operationTypeStringList.Zip(expectedOperationTypeList)) - // { - // var element = XElement.Parse($@" - // - // Assign Role - // Role - // https://localhost/v2/role/{{role}}/assign - // put - // AccessControl_AssignRole - // - // "); - // } - - // yield return new object[] - // { - // "Not applicable if no operationId XML tag is present", - // XElement.Parse(@" - // - // Assign Role - // Role - // - // "), - // false - // }; - - // yield return new object[] - // { - // "Applicable if one operationId XML tag is present", - // XElement.Parse(@" - // - // Assign Role - // Role - // AccessControl_AssignRole - // - // "), - // true - // }; - - // yield return new object[] - // { - // "Applicable if multiple operationId XML tags are present", - // XElement.Parse(@" - // - // AssignRole - // Assign Role - // Role - // AccessControl_AssignRole - // - // "), - // true - // }; - //} - public static IEnumerable GetTestCasesForApplyShouldUseTheOperationIdXmlTagAsOperationId() + [Fact] + public void ApplyShouldProcessTheXmlTagProperlyEndToEnd() { - yield return new object[] - { - "", - XElement.Parse(@" - - Assign Role - Role - - "), - false - }; - - yield return new object[] - { - "Applicable if one operationId XML tag is present", - XElement.Parse(@" + // Prepare + var element = XElement.Parse(@" Assign Role Role + https://localhost/v2/role/{role}/assign + put AccessControl_AssignRole - "), - true - }; + "); - yield return new object[] - { - "Applicable if multiple operationId XML tags are present", - XElement.Parse(@" - - AssignRole - Assign Role - Role - AccessControl_AssignRole - - "), - true - }; + var openApiPaths = new OpenApiPaths(); + var settings = new PreProcessingOperationFilterSettings(); + + var filter = new UsePredefinedOperationIdFilter(); + + // Action + filter.Apply(openApiPaths, element, settings); + + // Assert + string url = "/v2/role/{role}/assign"; + OperationType operationType = OperationType.Put; + var expectedOperationId = "AccessControl_AssignRole"; + + openApiPaths.Should().ContainKey(url); + openApiPaths[url].Operations.Should().ContainKey(operationType); + + openApiPaths[url].Operations[operationType] + .OperationId.Should().BeEquivalentTo(expectedOperationId); } private static string DefaultGetUrlFunc(XElement e) From 1d57d3a15324bcfd6f9a7fc30684b9a0edeb56b4 Mon Sep 17 00:00:00 2001 From: Tibor Takacs Date: Fri, 10 Jul 2020 10:04:36 +0100 Subject: [PATCH 3/4] Make test constructor internal and rename test classes. --- .../UsePredefinedOperationIdFilter.cs | 2 +- ...nHandler_GetUrlTests.cs => OperationHandlerGetUrlTests.cs} | 4 ++-- ...perationIdTests.cs => OperationHandlerOperationIdTests.cs} | 4 ++-- ...MethodTests.cs => OperationHandlerOperationMethodTests.cs} | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/{OperationHandler_GetUrlTests.cs => OperationHandlerGetUrlTests.cs} (96%) rename test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/{OperationHandler_OperationIdTests.cs => OperationHandlerOperationIdTests.cs} (97%) rename test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/{OperationHandler_OperationMethodTests.cs => OperationHandlerOperationMethodTests.cs} (95%) diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs index bc2b973..48b36ce 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/UsePredefinedOperationIdFilter.cs @@ -41,7 +41,7 @@ public UsePredefinedOperationIdFilter() /// Get operation method function. /// Get operation id function. /// Has operation id function. - public UsePredefinedOperationIdFilter( + internal UsePredefinedOperationIdFilter( Func GetUrlFunc, Func GetOperationMethodFunc, Func GetOperationIdFunc, diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_GetUrlTests.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandlerGetUrlTests.cs similarity index 96% rename from test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_GetUrlTests.cs rename to test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandlerGetUrlTests.cs index c7dfac7..6cd761f 100644 --- a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_GetUrlTests.cs +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandlerGetUrlTests.cs @@ -15,11 +15,11 @@ namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.FilterTests { [Collection("DefaultSettings")] - public class OperationHandler_GetUrlTests + public class OperationHandlerGetUrlTests { private readonly ITestOutputHelper _output; - public OperationHandler_GetUrlTests(ITestOutputHelper output) + public OperationHandlerGetUrlTests(ITestOutputHelper output) { _output = output; } diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationIdTests.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandlerOperationIdTests.cs similarity index 97% rename from test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationIdTests.cs rename to test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandlerOperationIdTests.cs index 4859a17..ad30f57 100644 --- a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationIdTests.cs +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandlerOperationIdTests.cs @@ -14,11 +14,11 @@ namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.FilterTests { [Collection("DefaultSettings")] - public class OperationHandler_OperationIdTests + public class OperationHandlerOperationIdTests { private readonly ITestOutputHelper _output; - public OperationHandler_OperationIdTests(ITestOutputHelper output) + public OperationHandlerOperationIdTests(ITestOutputHelper output) { _output = output; } diff --git a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationMethodTests.cs b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandlerOperationMethodTests.cs similarity index 95% rename from test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationMethodTests.cs rename to test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandlerOperationMethodTests.cs index 4d7244c..c471ecc 100644 --- a/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandler_OperationMethodTests.cs +++ b/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/OperationHandlerTests/OperationHandlerOperationMethodTests.cs @@ -16,11 +16,11 @@ namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests.FilterTests { [Collection("DefaultSettings")] - public class OperationHandler_OperationMethodTests + public class OperationHandlerOperationMethodTests { private readonly ITestOutputHelper _output; - public OperationHandler_OperationMethodTests(ITestOutputHelper output) + public OperationHandlerOperationMethodTests(ITestOutputHelper output) { _output = output; } From cddcace84373410468020a2cdda591b4e72af8f8 Mon Sep 17 00:00:00 2001 From: Tibor Takacs Date: Fri, 18 Sep 2020 14:47:03 +0100 Subject: [PATCH 4/4] PR comment fixes. --- .../OperationHandler.cs | 14 ++++++-------- .../CreateOperationMetaFilter.cs | 3 +-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/OperationHandler.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/OperationHandler.cs index 62f1ecd..8e24f93 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/OperationHandler.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/OperationHandler.cs @@ -25,7 +25,7 @@ internal static class OperationHandler /// True if the operationId tag is in the operation element, otherwise false. public static bool HasOperationId(XElement operationElement) { - return operationElement?.Elements().Where(p => p.Name == KnownXmlStrings.OperationId).Count() > 0; + return operationElement?.Elements().Where(p => p.Name == KnownXmlStrings.OperationId).Any() ?? false; } /// @@ -42,13 +42,11 @@ public static string GetOperationId(XElement operationElement) { return operationIdList[0].Value; } - else - { - string error = operationIdList.Count > 1 - ? SpecificationGenerationMessages.MultipleOperationId - : SpecificationGenerationMessages.NoOperationId; - throw new InvalidOperationIdException(error); - } + + string error = operationIdList.Count > 1 + ? SpecificationGenerationMessages.MultipleOperationId + : SpecificationGenerationMessages.NoOperationId; + throw new InvalidOperationIdException(error); } /// diff --git a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs index 57b9a9e..3ea090b 100644 --- a/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs +++ b/src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/PreprocessingOperationFilters/CreateOperationMetaFilter.cs @@ -13,7 +13,6 @@ namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.PreprocessingOp { /// /// Filter to initialize OpenApi operation based on the annotation XML element. - /// /// It does not do the creation itself but forwards the call to the real generator filters. /// The first filter which is applicable is executed in order to generate the operation. /// @@ -22,7 +21,7 @@ public class CreateOperationMetaFilter : IPreProcessingOperationFilter private List createOperationFilters; /// - /// Initializes a new production instance of the . + /// Initializes a new instance of the . /// /// /// Using this constructor, the following filter list is used: