From 2717e6e5cfbf00ac0cf69e457aa6328c3f91bedf Mon Sep 17 00:00:00 2001 From: Shweta Patil Date: Mon, 30 Jul 2018 16:19:41 -0700 Subject: [PATCH 1/9] - Updated model to store OpenApiDifference - Added various OpenApiComparer's to compare fragments of OpenApiDocument --- .../Services/ComparisonContext.cs | 79 ++++ .../Services/OpenApiComparer.cs | 19 +- .../Services/OpenApiComparerBase.cs | 107 +++++ .../Services/OpenApiComparerFactory.cs | 65 +++ .../Services/OpenApiDifference.cs | 27 +- .../Services/OpenApiDifferenceOperation.cs | 15 + .../Services/OpenApiDocumentComparer.cs | 38 ++ .../Services/OpenApiOperationComparer.cs | 66 +++ .../Services/OpenApiOperationsComparer.cs | 111 +++++ .../Services/OpenApiParameterComparer.cs | 34 ++ .../Services/OpenApiParametersComparer.cs | 87 ++++ .../Services/OpenApiPathItemComparer.cs | 48 ++ .../Services/OpenApiPathsComparer.cs | 109 +++++ .../Services/OpenApiComparerTestCases.cs | 439 ++++++++++++++++++ .../Services/OpenApiComparerTests.cs | 49 ++ 15 files changed, 1289 insertions(+), 4 deletions(-) create mode 100644 src/Microsoft.OpenApi/Services/ComparisonContext.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiDifferenceOperation.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiOperationsComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiPathsComparer.cs create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs diff --git a/src/Microsoft.OpenApi/Services/ComparisonContext.cs b/src/Microsoft.OpenApi/Services/ComparisonContext.cs new file mode 100644 index 000000000..b54fd6615 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/ComparisonContext.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// A class encapsulating the comarision context. + /// + public class ComparisonContext + { + private readonly IList _openApiDifferences = new List(); + private readonly Stack _path = new Stack(); + internal OpenApiComparerFactory OpenApiComparerFactory; + + /// + /// Creates instance of . + /// + /// + public ComparisonContext(OpenApiComparerFactory openApiComparerFactory) + { + OpenApiComparerFactory = openApiComparerFactory; + } + + /// + /// Gets the list of open api differences. + /// + public IEnumerable OpenApiDifferences => _openApiDifferences; + + /// + /// Pointer to the source of difference in the document. + /// + public string PathString => "#/" + string.Join("/", _path.Reverse()); + + /// + /// Adds an open api difference. + /// + /// The open api difference to add. + public void AddOpenApiDifference(OpenApiDifference openApiDifference) + { + if (openApiDifference == null) + { + throw Error.ArgumentNull(nameof(openApiDifference)); + } + + _openApiDifferences.Add(openApiDifference); + } + + /// + /// Allow Rule to indicate difference occured at a deeper context level. + /// + /// Identifier for the context. + public void Enter(string segment) + { + _path.Push(segment); + } + + /// + /// Exit from path context level. Enter and Exit calls should be matched. + /// + public void Exit() + { + _path.Pop(); + } + + /// + /// Gets the comparer instance for the requested type. + /// + /// Type of requested comparer. + /// Comparer instance to use when comparing requested type. + internal OpenApiComparerBase GetComparer() + { + return OpenApiComparerFactory.GetComparer(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiComparer.cs index a8faa625a..fbc216c5e 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparer.cs @@ -14,10 +14,23 @@ public static class OpenApiComparer /// /// Compares two s and returns a list of differences. /// - public static List Compare(OpenApiDocument source, OpenApiDocument target) + public static IEnumerable Compare(OpenApiDocument source, OpenApiDocument target) { - var diffs = new List(); - return diffs; + if (source == null) + { + throw Error.ArgumentNull(nameof(source)); + } + + if (target == null) + { + throw Error.ArgumentNull(nameof(target)); + } + + var comparisionContext = new ComparisonContext(new OpenApiComparerFactory()); + + new OpenApiDocumentComparer().Compare(source, target, comparisionContext); + + return comparisionContext.OpenApiDifferences; } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs new file mode 100644 index 000000000..2351f9277 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing parts of class. + /// + /// Type of class to compare. + public abstract class OpenApiComparerBase + { + /// + /// Validates a fragment of . + /// + /// The source fragment. + /// The target fragment. + /// Context under which to compare fragment. + public abstract void Compare(T sourceFragment, T targetFragment, ComparisonContext comparisonContext); + + /// + /// Compares two string object. + /// + /// The source string. + /// The target string. + /// The context under which to compare the objects. + internal void Compare(string source, string target, ComparisonContext comparisonContext) + { + if (string.IsNullOrWhiteSpace(source) && string.IsNullOrWhiteSpace(target)) + { + return; + } + + if (string.Compare(source, target, StringComparison.CurrentCultureIgnoreCase) != 0) + { + comparisonContext.AddOpenApiDifference(new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string), + SourceValue = source, + TargetValue = target, + Pointer = comparisonContext.PathString + }); + } + } + + /// + /// Compares two boolean object. + /// + /// The source. + /// The target. + /// The context under which to compare the objects. + internal void Compare(bool? source, bool? target, ComparisonContext comparisonContext) + { + if (source == null && target == null) + { + return; + } + + if (source != target) + { + comparisonContext.AddOpenApiDifference(new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool), + SourceValue = source, + TargetValue = target, + Pointer = comparisonContext.PathString + }); + } + } + + /// + /// Adds a segment to the context path to enable pointing to the current location in the document. + /// + /// The context under which to compare the objects. + /// An identifier for the segment. + /// The open api difference to add. + internal void WalkAndAddOpenApiDifference( + ComparisonContext comparisonContext, + string segment, + OpenApiDifference openApiDifference) + { + comparisonContext.Enter(segment.Replace("/", "~1")); + openApiDifference.Pointer = comparisonContext.PathString; + comparisonContext.AddOpenApiDifference(openApiDifference); + comparisonContext.Exit(); + } + + /// + /// Adds a segment to the context path to enable pointing to the current location in the document. + /// + /// The context under which to compare the objects. + /// An identifier for the segment. + /// An action that compares objects within the context. + protected virtual void WalkAndCompare( + ComparisonContext comparisonContext, + string segment, + Action compare) + { + comparisonContext.Enter(segment.Replace("/", "~1")); + compare(); + comparisonContext.Exit(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs b/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs new file mode 100644 index 000000000..f8ce27c1b --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for registering specific comparer instances and encapsulates default comparers. + /// + public class OpenApiComparerFactory + { + private static readonly Dictionary TypeToDefaultComparerMap = new Dictionary + { + {typeof(OpenApiPaths), new OpenApiPathsComparer()}, + {typeof(OpenApiPathItem), new OpenApiPathItemComparer()}, + {typeof(OpenApiOperation), new OpenApiOperationComparer()}, + {typeof(IDictionary), new OpenApiOperationsComparer()}, + {typeof(IList), new OpenApiParametersComparer()}, + {typeof(OpenApiParameter), new OpenApiParameterComparer()} + }; + + private readonly Dictionary _typeToComparerMap = new Dictionary(); + + /// + /// Adds a comparer instance to this registry. + /// + /// Type of the comparer instance. + /// Instance of to register. + protected void AddComparer(OpenApiComparerBase comparer) + { + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + _typeToComparerMap.Add(typeof(T), comparer); + } + + /// + /// Gets a registered comparer instance for the requested type. + /// + /// Type of the comparer. + /// The comparer instance corresponding to the type requested. + internal OpenApiComparerBase GetComparer() + { + var requestedComparerType = typeof(T); + + if (_typeToComparerMap.TryGetValue(requestedComparerType, out object comparerInstance)) + { + return (OpenApiComparerBase) comparerInstance; + } + + if (!TypeToDefaultComparerMap.TryGetValue(requestedComparerType, out comparerInstance)) + { + throw Error.NotSupported( + $"No comparer is registered for type {requestedComparerType.Name}."); + } + + return (OpenApiComparerBase) comparerInstance; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiDifference.cs b/src/Microsoft.OpenApi/Services/OpenApiDifference.cs index aea6d19f2..73cf6e3f7 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiDifference.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiDifference.cs @@ -1,14 +1,39 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Services { /// - /// Difference point between two + /// Difference point between two . /// public class OpenApiDifference { + /// + /// The type of the element for which difference found. + /// + public Type OpenApiComparedElementType { get; set; } + + /// + /// The open api difference operation. + /// + public OpenApiDifferenceOperation OpenApiDifferenceOperation { get; set; } + + /// + /// Pointer to the location of the difference. + /// + public string Pointer { get; set; } + + /// + /// The source value. + /// + public object SourceValue { get; set; } + + /// + /// The target value. + /// + public object TargetValue { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiDifferenceOperation.cs b/src/Microsoft.OpenApi/Services/OpenApiDifferenceOperation.cs new file mode 100644 index 000000000..3afc67268 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiDifferenceOperation.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.OpenApi.Services +{ + /// + /// The open api difference operation. + /// + public enum OpenApiDifferenceOperation + { + Add, + Remove, + Update + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs new file mode 100644 index 000000000..6460da81f --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiDocumentComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target + /// Context under which to compare the source and target. + public override void Compare( + OpenApiDocument sourceDocument, + OpenApiDocument targetDocument, + ComparisonContext comparisonContext) + { + comparisonContext.GetComparer().Compare( + sourceDocument.Paths, + targetDocument.Paths, + comparisonContext); + + // To Do Compare Info + // To Do Compare Servers + // To Do Compare Components + // To Do Compare Security Requirements + // To Do Compare Tags + // To Do Compare External Docs + // To Do Compare Extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs new file mode 100644 index 000000000..362bc65df --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiOperationComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiOperation sourceOperation, + OpenApiOperation targetOperation, + ComparisonContext comparisonContext) + { + if (sourceOperation == null && targetOperation == null) + { + return; + } + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Summary, + () => Compare(sourceOperation?.Summary, targetOperation?.Summary, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Summary, + () => Compare(sourceOperation?.Description, targetOperation?.Description, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.OperationId, + () => Compare(sourceOperation?.OperationId, targetOperation?.OperationId, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Deprecated, + () => Compare(sourceOperation?.Deprecated, targetOperation?.Deprecated, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Parameters, + () => comparisonContext + .GetComparer>() + .Compare(sourceOperation?.Parameters, targetOperation?.Parameters, comparisonContext)); + + + // Compare Responses + // Compare Request Body + // Compare CallBack + // Compare Security Requirements + // Compare Extensions + // Compare Servers + // Compare External Docs + // Compare Tags + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiOperationsComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiOperationsComparer.cs new file mode 100644 index 000000000..5b5c84ead --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiOperationsComparer.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of + /// where TKey is and TValue is . + /// + public class OpenApiOperationsComparer : OpenApiComparerBase> + { + /// + /// Executes comparision against source and target + /// where TKey is and TValue is . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + IDictionary sourceOperations, + IDictionary targetOperations, + ComparisonContext comparisonContext) + { + if (sourceOperations == null && targetOperations == null) + { + return; + } + + if (sourceOperations != null && targetOperations == null) + { + foreach (var sourceOperation in sourceOperations) + { + WalkAndAddOpenApiDifference( + comparisonContext, + sourceOperation.Key.GetDisplayName(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = sourceOperation, + OpenApiComparedElementType = typeof(KeyValuePair) + }); + } + + return; + } + + if (sourceOperations == null) + { + foreach (var targetOperation in targetOperations) + { + WalkAndAddOpenApiDifference( + comparisonContext, + targetOperation.Key.GetDisplayName(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + SourceValue = targetOperation, + OpenApiComparedElementType = typeof(KeyValuePair) + }); + } + + return; + } + + var newOperationKeysInTarget = targetOperations.Keys.Except(sourceOperations.Keys).ToList(); + + foreach (var newOperationKeyInTarget in newOperationKeysInTarget) + { + WalkAndAddOpenApiDifference( + comparisonContext, + newOperationKeyInTarget.GetDisplayName(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = new KeyValuePair( + newOperationKeyInTarget, + targetOperations[newOperationKeyInTarget]), + OpenApiComparedElementType = typeof(KeyValuePair) + }); + } + + foreach (var sourceOperation in sourceOperations) + { + if (targetOperations.Keys.Contains(sourceOperation.Key)) + { + WalkAndCompare(comparisonContext, sourceOperation.Key.GetDisplayName(), + () => comparisonContext + .GetComparer() + .Compare(sourceOperation.Value, targetOperations[sourceOperation.Key], comparisonContext)); + } + else + { + WalkAndAddOpenApiDifference( + comparisonContext, + sourceOperation.Key.GetDisplayName(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = sourceOperation, + OpenApiComparedElementType = typeof(KeyValuePair) + }); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs new file mode 100644 index 000000000..755210cda --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiParameterComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiParameter sourceParameter, + OpenApiParameter targetParameter, + ComparisonContext comparisonContext) + { + if (sourceParameter == null && targetParameter == null) + { + } + + // To Do Compare Schema + // To Do Compare Content + // To Do Compare Examples + // To Do Compare parameter as IOpenApiExtensible + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs new file mode 100644 index 000000000..545ef5dbf --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of + /// where T is. + /// + public class OpenApiParametersComparer : OpenApiComparerBase> + { + /// + /// Executes comparision against source and target + /// where T is. + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + IList sourceParameters, + IList targetParameters, + ComparisonContext comparisonContext) + { + if (sourceParameters == null && targetParameters == null) + { + return; + } + + if (!sourceParameters.Any() && !targetParameters.Any()) + { + return; + } + + var newParametersInTarget = targetParameters?.Where( + targetParam => !sourceParameters.Any( + sourceParam => sourceParam.Name == targetParam.Name && sourceParam.In == targetParam.In)).ToList(); + + for (var i = 0; i < newParametersInTarget?.Count; i++) + { + WalkAndAddOpenApiDifference( + comparisonContext, + i.ToString(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = targetParameters[i], + OpenApiComparedElementType = typeof(OpenApiParameter) + }); + } + + var removedParameters = sourceParameters?.Where( + sourceParam => !targetParameters.Any( + targetParam => sourceParam.Name == targetParam.Name && sourceParam.In == targetParam.In)).ToList(); + + for (var i = 0; i < removedParameters.Count; i++) + { + WalkAndAddOpenApiDifference( + comparisonContext, + i.ToString(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = removedParameters[i], + OpenApiComparedElementType = typeof(OpenApiParameter) + }); + } + + for (var i = 0; i < sourceParameters.Count; i++) + { + var sourceParameter = sourceParameters[i]; + var targetParameter = targetParameters + .FirstOrDefault(param => param.Name == sourceParameter.Name && param.In == sourceParameter.In); + + WalkAndCompare( + comparisonContext, + i.ToString(), + () => comparisonContext + .GetComparer() + .Compare(sourceParameter, targetParameter, comparisonContext)); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs new file mode 100644 index 000000000..7ae1ef758 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiPathItemComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiPathItem sourcePathItem, + OpenApiPathItem targetPathItem, + ComparisonContext comparisonContext) + { + if (sourcePathItem == null && targetPathItem == null) + { + return; + } + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Summary, + () => Compare(sourcePathItem?.Summary, targetPathItem?.Description, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Description, + () => Compare(sourcePathItem?.Description, targetPathItem?.Description, comparisonContext)); + + comparisonContext.GetComparer>() + .Compare(sourcePathItem?.Operations, targetPathItem?.Operations, comparisonContext); + + // To Do Compare Servers + // To Do Compare Parameters + // To Do Compare Extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiPathsComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiPathsComparer.cs new file mode 100644 index 000000000..684cfcabb --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiPathsComparer.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Linq; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiPathsComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare(OpenApiPaths sourcePaths, OpenApiPaths targetPaths, + ComparisonContext comparisonContext) + { + if (sourcePaths == null && targetPaths == null) + { + return; + } + + comparisonContext.Enter(OpenApiConstants.Paths); + + if (sourcePaths != null && targetPaths == null) + { + foreach (var sourcePathKey in sourcePaths.Keys) + { + WalkAndAddOpenApiDifference( + comparisonContext, + sourcePathKey, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = sourcePaths[sourcePathKey], + OpenApiComparedElementType = typeof(OpenApiPathItem) + }); + } + + return; + } + + if (sourcePaths == null) + { + foreach (var targetPathKey in targetPaths.Keys) + { + WalkAndAddOpenApiDifference( + comparisonContext, + targetPathKey, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = targetPaths[targetPathKey], + OpenApiComparedElementType = typeof(OpenApiPathItem) + }); + } + + return; + } + + var newPathKeysInTarget = targetPaths.Keys.Except(sourcePaths?.Keys).ToList(); + + foreach (var newPathKeyInTarget in newPathKeysInTarget) + { + WalkAndAddOpenApiDifference( + comparisonContext, + newPathKeyInTarget, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = targetPaths[newPathKeyInTarget], + OpenApiComparedElementType = typeof(OpenApiPathItem) + }); + } + + foreach (var sourcePathKey in sourcePaths.Keys) + { + if (targetPaths.ContainsKey(sourcePathKey)) + { + WalkAndCompare( + comparisonContext, + sourcePathKey, + () => comparisonContext + .GetComparer() + .Compare( sourcePaths[sourcePathKey], targetPaths[sourcePathKey], comparisonContext ) ); + } + else + { + WalkAndAddOpenApiDifference( + comparisonContext, + sourcePathKey, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = sourcePaths[sourcePathKey], + OpenApiComparedElementType = typeof(OpenApiPathItem) + }); + } + } + + comparisonContext.Exit(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs new file mode 100644 index 000000000..cdf4d4457 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs @@ -0,0 +1,439 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; + +namespace Microsoft.OpenApi.Tests.Services +{ + internal static class OpenApiComparerTestCases + { + public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed() + { + // New and removed paths + yield return new object[] + { + "New And Removed Paths", + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } + } + } + }, + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/newPath", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } + } + } + }, + new List + { + new OpenApiDifference() + { + Pointer = "#/paths/~1newPath", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(OpenApiPathItem) + }, + new OpenApiDifference() + { + Pointer = "#/paths/~1test", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(OpenApiPathItem) + } + } + }; + + // New and removed operations + yield return new object[] + { + "New And Removed Operations", + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + }, + { + OperationType.Post, new OpenApiOperation() + } + } + } + } + } + }, + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + }, + { + OperationType.Patch, new OpenApiOperation() + } + } + } + } + } + }, + new List + { + new OpenApiDifference() + { + Pointer = "#/paths/~1test/patch", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference() + { + Pointer = "#/paths/~1test/post", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + + // Empty target document paths + yield return new object[] + { + "Empty target document", + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } + } + } + }, + new OpenApiDocument(), + new List + { + new OpenApiDifference() + { + Pointer = "#/paths/~1test", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(OpenApiPathItem) + } + } + }; + + // Empty source document + yield return new object[] + { + "Empty source document", + new OpenApiDocument(), + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/newPath", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } + } + } + }, + new List + { + new OpenApiDifference() + { + Pointer = "#/paths/~1newPath", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(OpenApiPathItem) + } + } + }; + + // Empty target operations + yield return new object[] + { + "Empty target operations", + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + }, + { + OperationType.Post, new OpenApiOperation() + } + } + } + } + } + }, + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary() + } + } + } + }, + new List + { + new OpenApiDifference() + { + Pointer = "#/paths/~1test/get", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference() + { + Pointer = "#/paths/~1test/post", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + + // Empty source operations + yield return new object[] + { + "Empty source operations", + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary() + } + } + } + }, + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + }, + { + OperationType.Patch, new OpenApiOperation() + } + } + } + } + } + }, + new List + { + new OpenApiDifference() + { + Pointer = "#/paths/~1test/get", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference() + { + Pointer = "#/paths/~1test/patch", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + + // Identical source and target + yield return new object[] + { + "Identical source and target documents", + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + }, + { + OperationType.Post, new OpenApiOperation() + } + } + } + } + } + }, + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + }, + { + OperationType.Post, new OpenApiOperation() + } + } + } + } + } + }, + new List() + }; + + // Differences in summary and description + yield return new object[] + { + "Differences in summary and description", + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + }, + { + OperationType.Post, new OpenApiOperation() + } + } + } + } + } + }, + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "updated", + Description = "updated", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + }, + { + OperationType.Post, new OpenApiOperation() + } + } + } + } + } + }, + new List + { + new OpenApiDifference() + { + Pointer = "#/paths/~1test/summary", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string) + }, + new OpenApiDifference() + { + Pointer = "#/paths/~1test/description", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string) + } + } + }; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs new file mode 100644 index 000000000..947f659b7 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.Tests.Services +{ + [Collection("DefaultSettings")] + public class OpenApiComparerTests + { + private readonly ITestOutputHelper _output; + + public OpenApiComparerTests(ITestOutputHelper output) + { + _output = output; + } + + [Theory] + [MemberData( + nameof(OpenApiComparerTestCases.GetTestCasesForOpenApiComparerShouldSucceed), + MemberType = typeof(OpenApiComparerTestCases))] + public void OpenApiComparerShouldSucceed( + string testCaseName, + OpenApiDocument source, + OpenApiDocument target, + List expectedDifferences) + { + _output.WriteLine(testCaseName); + + var differences = OpenApiComparer.Compare(source, target).ToList(); + differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); + + for (int i = 0; i < differences.Count(); i++) + { + differences[i].Pointer.ShouldBeEquivalentTo(expectedDifferences[i].Pointer); + differences[i].OpenApiComparedElementType + .ShouldBeEquivalentTo( expectedDifferences[i].OpenApiComparedElementType ); + differences[i].OpenApiDifferenceOperation + .ShouldBeEquivalentTo( expectedDifferences[i].OpenApiDifferenceOperation ); + } + } + } +} \ No newline at end of file From a3f3537971a53c727d9220528a744060175c7525 Mon Sep 17 00:00:00 2001 From: Shweta Patil Date: Fri, 3 Aug 2018 14:40:55 -0700 Subject: [PATCH 2/9] Fix typo. --- src/Microsoft.OpenApi/Services/ComparisonContext.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/ComparisonContext.cs b/src/Microsoft.OpenApi/Services/ComparisonContext.cs index b54fd6615..da5d29772 100644 --- a/src/Microsoft.OpenApi/Services/ComparisonContext.cs +++ b/src/Microsoft.OpenApi/Services/ComparisonContext.cs @@ -3,12 +3,11 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Services { /// - /// A class encapsulating the comarision context. + /// A class encapsulating the comparision context. /// public class ComparisonContext { From 3c3e74f0f7c3eb48bcd9ce9b1403179f32161fd1 Mon Sep 17 00:00:00 2001 From: Shweta Patil Date: Mon, 13 Aug 2018 00:03:20 -0700 Subject: [PATCH 3/9] - Added comparer for Schema, RequestBody, Responses, MediaType --- .../Services/ComparisonContext.cs | 13 +- .../Services/OpenApiComparer.cs | 2 +- .../Services/OpenApiComparerBase.cs | 29 +- .../Services/OpenApiComparerFactory.cs | 8 +- .../Services/OpenApiDictionaryComparer.cs | 90 +++ .../Services/OpenApiDocumentComparer.cs | 10 +- .../Services/OpenApiMediaTypeComparer.cs | 54 ++ .../Services/OpenApiOperationComparer.cs | 11 +- .../Services/OpenApiOperationsComparer.cs | 41 +- .../Services/OpenApiParameterComparer.cs | 31 +- .../Services/OpenApiParametersComparer.cs | 13 + .../Services/OpenApiPathItemComparer.cs | 8 +- .../Services/OpenApiPathsComparer.cs | 51 +- .../Services/OpenApiRequestBodyComparer.cs | 91 +++ .../Services/OpenApiResponseComparer.cs | 90 +++ .../Services/OpenApiSchemaComparer.cs | 214 ++++++ .../Services/OpenApiComparerTestCases.cs | 617 ++++++++++++++++- .../Services/OpenApiComparerTests.cs | 6 +- .../OpenApiRequestBodyComparerTests.cs | 508 ++++++++++++++ .../Services/OpenApiResponsesComparerTests.cs | 653 ++++++++++++++++++ 20 files changed, 2431 insertions(+), 109 deletions(-) create mode 100644 src/Microsoft.OpenApi/Services/OpenApiDictionaryComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiRequestBodyComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs diff --git a/src/Microsoft.OpenApi/Services/ComparisonContext.cs b/src/Microsoft.OpenApi/Services/ComparisonContext.cs index da5d29772..02b6e20e1 100644 --- a/src/Microsoft.OpenApi/Services/ComparisonContext.cs +++ b/src/Microsoft.OpenApi/Services/ComparisonContext.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Services { @@ -13,15 +14,23 @@ public class ComparisonContext { private readonly IList _openApiDifferences = new List(); private readonly Stack _path = new Stack(); + internal readonly OpenApiDocument SourceDocument; + internal readonly Stack SourceSchemaLoop = new Stack(); + internal readonly OpenApiDocument TargetDocument; + internal readonly Stack TargetSchemaLoop = new Stack(); internal OpenApiComparerFactory OpenApiComparerFactory; /// /// Creates instance of . /// - /// - public ComparisonContext(OpenApiComparerFactory openApiComparerFactory) + public ComparisonContext( + OpenApiComparerFactory openApiComparerFactory, + OpenApiDocument sourceDocument, + OpenApiDocument targetDocument) { OpenApiComparerFactory = openApiComparerFactory; + SourceDocument = sourceDocument; + TargetDocument = targetDocument; } /// diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiComparer.cs index fbc216c5e..f0b2744cf 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparer.cs @@ -26,7 +26,7 @@ public static IEnumerable Compare(OpenApiDocument source, Ope throw Error.ArgumentNull(nameof(target)); } - var comparisionContext = new ComparisonContext(new OpenApiComparerFactory()); + var comparisionContext = new ComparisonContext(new OpenApiComparerFactory(), source, target); new OpenApiDocumentComparer().Compare(source, target, comparisionContext); diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs index 2351f9277..dd8fa760a 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs @@ -2,11 +2,12 @@ // Licensed under the MIT license. using System; +using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Services { /// - /// Defines behavior for comparing parts of class. + /// Defines behavior for comparing parts of class. /// /// Type of class to compare. public abstract class OpenApiComparerBase @@ -71,6 +72,32 @@ internal void Compare(bool? source, bool? target, ComparisonContext comparisonCo } } + /// + /// Compares two decimal object. + /// + /// The source. + /// The target. + /// The context under which to compare the objects. + internal void Compare(decimal? source, decimal? target, ComparisonContext comparisonContext) + { + if (source == null && target == null) + { + return; + } + + if (source != target) + { + comparisonContext.AddOpenApiDifference(new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(decimal), + SourceValue = source, + TargetValue = target, + Pointer = comparisonContext.PathString + }); + } + } + /// /// Adds a segment to the context path to enable pointing to the current location in the document. /// diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs b/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs index f8ce27c1b..780ea0e7c 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs @@ -19,7 +19,13 @@ public class OpenApiComparerFactory {typeof(OpenApiOperation), new OpenApiOperationComparer()}, {typeof(IDictionary), new OpenApiOperationsComparer()}, {typeof(IList), new OpenApiParametersComparer()}, - {typeof(OpenApiParameter), new OpenApiParameterComparer()} + {typeof(OpenApiParameter), new OpenApiParameterComparer()}, + {typeof(OpenApiSchema), new OpenApiSchemaComparer()}, + {typeof(OpenApiMediaType), new OpenApiMediaTypeComparer()}, + {typeof(IDictionary), new OpenApiDictionaryComparer()}, + {typeof(IDictionary), new OpenApiDictionaryComparer()}, + {typeof(OpenApiRequestBody), new OpenApiRequestBodyComparer()}, + {typeof(OpenApiResponse), new OpenApiResponseComparer()} }; private readonly Dictionary _typeToComparerMap = new Dictionary(); diff --git a/src/Microsoft.OpenApi/Services/OpenApiDictionaryComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiDictionaryComparer.cs new file mode 100644 index 000000000..0a82ad0b1 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiDictionaryComparer.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OpenApi.Interfaces; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing where TKey is + /// and TValue is . + /// + public class OpenApiDictionaryComparer : OpenApiComparerBase> + where T : IOpenApiSerializable + { + /// + /// Executes comparision against source and target + /// where TKey is and TValue is . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + IDictionary sourceFragment, + IDictionary targetFragment, + ComparisonContext comparisonContext) + { + if (sourceFragment == null && targetFragment == null) + { + return; + } + + if (sourceFragment == null || targetFragment == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceFragment, + TargetValue = targetFragment, + OpenApiComparedElementType = typeof(IDictionary), + Pointer = comparisonContext.PathString + }); + + return; + } + + var newKeysInTarget = targetFragment.Keys.Except(sourceFragment.Keys).ToList(); + + foreach (var newKeyInTarget in newKeysInTarget) + { + WalkAndAddOpenApiDifference( + comparisonContext, + newKeyInTarget, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = new KeyValuePair( + newKeyInTarget, + targetFragment[newKeyInTarget]), + OpenApiComparedElementType = typeof(KeyValuePair) + }); + } + + foreach (var source in sourceFragment) + { + if (targetFragment.Keys.Contains(source.Key)) + { + WalkAndCompare(comparisonContext, source.Key, + () => comparisonContext + .GetComparer() + .Compare(source.Value, targetFragment[source.Key], comparisonContext)); + } + else + { + WalkAndAddOpenApiDifference( + comparisonContext, + source.Key, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = source, + OpenApiComparedElementType = typeof(KeyValuePair) + }); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs index 6460da81f..625e7ac46 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs @@ -21,10 +21,12 @@ public override void Compare( OpenApiDocument targetDocument, ComparisonContext comparisonContext) { - comparisonContext.GetComparer().Compare( - sourceDocument.Paths, - targetDocument.Paths, - comparisonContext); + WalkAndCompare( + comparisonContext, + OpenApiConstants.Paths, + () => comparisonContext + .GetComparer() + .Compare(sourceDocument.Paths, targetDocument.Paths, comparisonContext)); // To Do Compare Info // To Do Compare Servers diff --git a/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs new file mode 100644 index 000000000..8388afcbd --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiMediaTypeComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiMediaType sourceMediaType, + OpenApiMediaType targetMediaType, + ComparisonContext comparisonContext) + { + if (sourceMediaType == null && targetMediaType == null) + { + return; + } + + if (sourceMediaType == null || targetMediaType == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceMediaType, + TargetValue = targetMediaType, + OpenApiComparedElementType = typeof(OpenApiMediaType), + Pointer = comparisonContext.PathString + }); + + return; + } + + comparisonContext + .GetComparer() + .Compare(sourceMediaType.Schema, targetMediaType.Schema, comparisonContext); + + // To Do Compare Example + // To Do Compare Examples + // To Do Compare Encoding + // To Do Compare Extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs index 362bc65df..28a931665 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs @@ -52,9 +52,16 @@ public override void Compare( .GetComparer>() .Compare(sourceOperation?.Parameters, targetOperation?.Parameters, comparisonContext)); + WalkAndCompare(comparisonContext, OpenApiConstants.RequestBody, + () => comparisonContext + .GetComparer() + .Compare(sourceOperation?.RequestBody, targetOperation?.RequestBody, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Responses, + () => comparisonContext + .GetComparer>() + .Compare(sourceOperation?.Responses, targetOperation?.Responses, comparisonContext)); - // Compare Responses - // Compare Request Body // Compare CallBack // Compare Security Requirements // Compare Extensions diff --git a/src/Microsoft.OpenApi/Services/OpenApiOperationsComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiOperationsComparer.cs index 5b5c84ead..0aace1a3e 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiOperationsComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiOperationsComparer.cs @@ -31,38 +31,17 @@ public override void Compare( return; } - if (sourceOperations != null && targetOperations == null) + if (sourceOperations == null || targetOperations == null) { - foreach (var sourceOperation in sourceOperations) - { - WalkAndAddOpenApiDifference( - comparisonContext, - sourceOperation.Key.GetDisplayName(), - new OpenApiDifference - { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - SourceValue = sourceOperation, - OpenApiComparedElementType = typeof(KeyValuePair) - }); - } - - return; - } - - if (sourceOperations == null) - { - foreach (var targetOperation in targetOperations) - { - WalkAndAddOpenApiDifference( - comparisonContext, - targetOperation.Key.GetDisplayName(), - new OpenApiDifference - { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - SourceValue = targetOperation, - OpenApiComparedElementType = typeof(KeyValuePair) - }); - } + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceOperations, + TargetValue = targetOperations, + OpenApiComparedElementType = typeof(IDictionary), + Pointer = comparisonContext.PathString + }); return; } diff --git a/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs index 755210cda..ca4241cf6 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Collections.Generic; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Services @@ -23,10 +24,36 @@ public override void Compare( { if (sourceParameter == null && targetParameter == null) { + return; } - // To Do Compare Schema - // To Do Compare Content + if (sourceParameter == null || targetParameter == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceParameter, + TargetValue = targetParameter, + OpenApiComparedElementType = typeof(OpenApiParameter), + Pointer = comparisonContext.PathString + }); + + return; + } + + comparisonContext.GetComparer().Compare( + sourceParameter?.Schema, + targetParameter?.Schema, + comparisonContext); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Content, + () => comparisonContext + .GetComparer>() + .Compare(sourceParameter.Content, targetParameter.Content, comparisonContext)); + // To Do Compare Examples // To Do Compare parameter as IOpenApiExtensible } diff --git a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs index 545ef5dbf..0317501a7 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs @@ -75,6 +75,19 @@ public override void Compare( var targetParameter = targetParameters .FirstOrDefault(param => param.Name == sourceParameter.Name && param.In == sourceParameter.In); + if (targetParameter == null) + { + WalkAndAddOpenApiDifference( + comparisonContext, i.ToString(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = sourceParameter, + OpenApiComparedElementType = typeof(OpenApiParameter) + }); + return; + } + WalkAndCompare( comparisonContext, i.ToString(), diff --git a/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs index 7ae1ef758..cf5846c11 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs @@ -40,8 +40,14 @@ public override void Compare( comparisonContext.GetComparer>() .Compare(sourcePathItem?.Operations, targetPathItem?.Operations, comparisonContext); + WalkAndCompare( + comparisonContext, + OpenApiConstants.Parameters, + () => comparisonContext + .GetComparer>() + .Compare(sourcePathItem?.Parameters, targetPathItem?.Parameters, comparisonContext)); + // To Do Compare Servers - // To Do Compare Parameters // To Do Compare Extensions } } diff --git a/src/Microsoft.OpenApi/Services/OpenApiPathsComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiPathsComparer.cs index 684cfcabb..a94ac8349 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiPathsComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiPathsComparer.cs @@ -17,7 +17,9 @@ public class OpenApiPathsComparer : OpenApiComparerBase /// The source. /// The target. /// Context under which to compare the source and target. - public override void Compare(OpenApiPaths sourcePaths, OpenApiPaths targetPaths, + public override void Compare( + OpenApiPaths sourcePaths, + OpenApiPaths targetPaths, ComparisonContext comparisonContext) { if (sourcePaths == null && targetPaths == null) @@ -25,40 +27,17 @@ public override void Compare(OpenApiPaths sourcePaths, OpenApiPaths targetPaths, return; } - comparisonContext.Enter(OpenApiConstants.Paths); - - if (sourcePaths != null && targetPaths == null) + if (sourcePaths == null || targetPaths == null) { - foreach (var sourcePathKey in sourcePaths.Keys) - { - WalkAndAddOpenApiDifference( - comparisonContext, - sourcePathKey, - new OpenApiDifference - { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - SourceValue = sourcePaths[sourcePathKey], - OpenApiComparedElementType = typeof(OpenApiPathItem) - }); - } - - return; - } - - if (sourcePaths == null) - { - foreach (var targetPathKey in targetPaths.Keys) - { - WalkAndAddOpenApiDifference( - comparisonContext, - targetPathKey, - new OpenApiDifference - { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - TargetValue = targetPaths[targetPathKey], - OpenApiComparedElementType = typeof(OpenApiPathItem) - }); - } + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourcePaths, + TargetValue = targetPaths, + OpenApiComparedElementType = typeof(OpenApiPaths), + Pointer = comparisonContext.PathString + }); return; } @@ -87,7 +66,7 @@ public override void Compare(OpenApiPaths sourcePaths, OpenApiPaths targetPaths, sourcePathKey, () => comparisonContext .GetComparer() - .Compare( sourcePaths[sourcePathKey], targetPaths[sourcePathKey], comparisonContext ) ); + .Compare(sourcePaths[sourcePathKey], targetPaths[sourcePathKey], comparisonContext)); } else { @@ -102,8 +81,6 @@ public override void Compare(OpenApiPaths sourcePaths, OpenApiPaths targetPaths, }); } } - - comparisonContext.Exit(); } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiRequestBodyComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiRequestBodyComparer.cs new file mode 100644 index 000000000..af8353f32 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiRequestBodyComparer.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiRequestBodyComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiRequestBody sourceRequestBody, + OpenApiRequestBody targetRequestBody, + ComparisonContext comparisonContext) + { + if (sourceRequestBody == null && targetRequestBody == null) + { + return; + } + + if (sourceRequestBody == null || targetRequestBody == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceRequestBody, + TargetValue = targetRequestBody, + OpenApiComparedElementType = typeof(OpenApiRequestBody), + Pointer = comparisonContext.PathString + }); + + return; + } + + if (sourceRequestBody.Reference != null + && targetRequestBody.Reference != null + && sourceRequestBody.Reference.Id != targetRequestBody.Reference.Id) + { + WalkAndAddOpenApiDifference( + comparisonContext, + "$ref", + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceRequestBody.Reference, + TargetValue = targetRequestBody.Reference, + OpenApiComparedElementType = typeof(OpenApiReference) + }); + + return; + } + + if (sourceRequestBody.Reference != null) + { + sourceRequestBody = (OpenApiRequestBody) comparisonContext.SourceDocument.ResolveReference( + sourceRequestBody.Reference); + } + + if (targetRequestBody.Reference != null) + { + targetRequestBody = (OpenApiRequestBody) comparisonContext.TargetDocument.ResolveReference( + targetRequestBody.Reference); + } + + WalkAndCompare(comparisonContext, OpenApiConstants.Description, + () => Compare(sourceRequestBody.Description, targetRequestBody.Description, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Required, + () => Compare(sourceRequestBody.Required, targetRequestBody.Required, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Content, + () => comparisonContext + .GetComparer>() + .Compare(sourceRequestBody.Content, targetRequestBody.Content, comparisonContext)); + + //To Do Compare Extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs new file mode 100644 index 000000000..5f2306b2f --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiResponseComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiResponse sourceResponse, + OpenApiResponse targetResponse, + ComparisonContext comparisonContext) + { + if (sourceResponse == null && targetResponse == null) + { + return; + } + + if (sourceResponse == null || targetResponse == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceResponse, + TargetValue = targetResponse, + OpenApiComparedElementType = typeof(OpenApiResponse), + Pointer = comparisonContext.PathString + }); + + return; + } + + if (sourceResponse.Reference != null + && targetResponse.Reference != null + && sourceResponse.Reference.Id != targetResponse.Reference.Id) + { + WalkAndAddOpenApiDifference( + comparisonContext, + "$ref", + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceResponse.Reference, + TargetValue = targetResponse.Reference, + OpenApiComparedElementType = typeof(OpenApiReference) + }); + + return; + } + + if (sourceResponse.Reference != null) + { + sourceResponse = (OpenApiResponse) comparisonContext.SourceDocument.ResolveReference( + sourceResponse.Reference); + } + + if (targetResponse.Reference != null) + { + targetResponse = (OpenApiResponse) comparisonContext.TargetDocument.ResolveReference( + targetResponse.Reference); + } + + WalkAndCompare(comparisonContext, OpenApiConstants.Description, + () => Compare(sourceResponse.Description, targetResponse.Description, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Content, + () => comparisonContext + .GetComparer>() + .Compare(sourceResponse.Content, targetResponse.Content, comparisonContext)); + + // To Do Compare Headers + // To Do Compare Link + // To Do Compare Extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs new file mode 100644 index 000000000..5675bbb9d --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiSchemaComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiSchema sourceSchema, + OpenApiSchema targetSchema, + ComparisonContext comparisonContext) + { + if (sourceSchema == null && targetSchema == null) + { + return; + } + + if (sourceSchema == null || targetSchema == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceSchema, + TargetValue = targetSchema, + OpenApiComparedElementType = typeof(OpenApiSchema), + Pointer = comparisonContext.PathString + }); + + return; + } + + if (comparisonContext.SourceSchemaLoop.Contains(sourceSchema) + || comparisonContext.SourceSchemaLoop.Contains(targetSchema)) + { + return; // Loop detected, this schema has already been walked. + } + + comparisonContext.SourceSchemaLoop.Push(sourceSchema); + comparisonContext.TargetSchemaLoop.Push(targetSchema); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Title, + () => Compare(sourceSchema.Title, targetSchema.Title, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Maximum, + () => Compare(sourceSchema.Maximum, targetSchema.Maximum, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.MultipleOf, + () => Compare(sourceSchema.MultipleOf, targetSchema.MultipleOf, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.ExclusiveMaximum, + () => Compare(sourceSchema.ExclusiveMaximum, targetSchema.ExclusiveMaximum, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Minimum, + () => Compare(sourceSchema.Minimum, targetSchema.Minimum, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.ExclusiveMinimum, + () => Compare(sourceSchema.ExclusiveMinimum, targetSchema.ExclusiveMinimum, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.MaxLength, + () => Compare(sourceSchema.MaxLength, targetSchema.MaxLength, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.MinLength, + () => Compare(sourceSchema.MinLength, targetSchema.MinLength, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.MaxItems, + () => Compare(sourceSchema.MaxItems, targetSchema.MaxItems, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.MinItems, + () => Compare(sourceSchema.MinItems, targetSchema.MinItems, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Format, + () => Compare(sourceSchema.Format, targetSchema.Format, comparisonContext)); + + if (sourceSchema.Type != targetSchema.Type) + { + WalkAndAddOpenApiDifference( + comparisonContext, + OpenApiConstants.Type, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceSchema.Type, + TargetValue = targetSchema.Type, + OpenApiComparedElementType = typeof(string) + }); + + return; + } + + if (sourceSchema.Items != null && targetSchema.Items != null) + { + WalkAndCompare( + comparisonContext, + "items", + () => comparisonContext + .GetComparer() + .Compare(sourceSchema.Items, targetSchema.Items, comparisonContext)); + } + + if (sourceSchema.Reference != null + && targetSchema.Reference != null + && sourceSchema.Reference.Id != targetSchema.Reference.Id) + { + WalkAndAddOpenApiDifference( + comparisonContext, + "$ref", + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceSchema.Reference?.Id, + TargetValue = targetSchema.Reference?.Id, + OpenApiComparedElementType = typeof(string) + }); + + return; + } + + if (sourceSchema.Reference != null) + { + sourceSchema = (OpenApiSchema) comparisonContext.SourceDocument.ResolveReference( + sourceSchema.Reference); + } + + if (targetSchema.Reference != null) + { + targetSchema = (OpenApiSchema) comparisonContext.TargetDocument.ResolveReference( + targetSchema.Reference); + } + + if (targetSchema.Properties != null) + { + IEnumerable newPropertiesInTarget = sourceSchema.Properties == null + ? targetSchema.Properties.Keys + : targetSchema.Properties.Keys.Except(sourceSchema.Properties.Keys) + .ToList(); + + WalkAndCompare(comparisonContext, OpenApiConstants.Properties, () => + { + foreach (var newPropertyInTarget in newPropertiesInTarget) + { + WalkAndAddOpenApiDifference( + comparisonContext, + newPropertyInTarget, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = new KeyValuePair(newPropertyInTarget, + targetSchema.Properties[newPropertyInTarget]), + OpenApiComparedElementType = typeof(KeyValuePair) + }); + } + }); + } + + if (sourceSchema.Properties != null) + { + WalkAndCompare(comparisonContext, OpenApiConstants.Properties, () => + { + foreach (var sourceSchemaProperty in sourceSchema.Properties) + { + if (targetSchema.Properties.ContainsKey(sourceSchemaProperty.Key)) + { + WalkAndCompare( + comparisonContext, + sourceSchemaProperty.Key, + () => comparisonContext + .GetComparer() + .Compare(sourceSchemaProperty.Value, + targetSchema.Properties[sourceSchemaProperty.Key], comparisonContext)); + } + else + { + WalkAndAddOpenApiDifference( + comparisonContext, + sourceSchemaProperty.Key, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + TargetValue = sourceSchemaProperty, + OpenApiComparedElementType = typeof(KeyValuePair) + }); + } + } + }); + } + + // To Do Compare schema.AllOf + // To Do Compare schema.AnyOf + // To Do compare external Docs + // To Do compare schema as IOpenApiExtensible + + comparisonContext.SourceSchemaLoop.Pop(); + comparisonContext.TargetSchemaLoop.Pop(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs index cdf4d4457..9280c1a93 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Services; @@ -55,13 +57,13 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1newPath", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(OpenApiPathItem) }, - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, @@ -120,17 +122,17 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/patch", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair) }, - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/post", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair) } } }; @@ -161,11 +163,11 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( new OpenApiDocument(), new List { - new OpenApiDifference() + new OpenApiDifference { - Pointer = "#/paths/~1test", - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(OpenApiPathItem) + Pointer = "#/paths", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiPaths) } } }; @@ -196,11 +198,11 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { - Pointer = "#/paths/~1newPath", - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(OpenApiPathItem) + Pointer = "#/paths", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiPaths) } } }; @@ -247,17 +249,17 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/get", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair) }, - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/post", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair) } } }; @@ -304,17 +306,17 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/get", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair) }, - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/patch", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair) } } }; @@ -420,13 +422,13 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/summary", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, OpenApiComparedElementType = typeof(string) }, - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/description", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, @@ -434,6 +436,573 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( } } }; + + // Differences in schema + yield return new object[] + { + "Differences in schema", + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + Parameters = new List + { + new OpenApiParameter + { + Name = "Test Parameter", + In = ParameterLocation.Path, + Schema = new OpenApiSchema + { + Title = "title1", + MultipleOf = 3, + Maximum = 42, + ExclusiveMinimum = true, + Minimum = 10, + Default = new OpenApiInteger(15), + Type = "integer", + + Nullable = true, + ExternalDocs = new OpenApiExternalDocs + { + Url = new Uri("http://example.com/externalDocs") + }, + + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + } + } + }, + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + }, + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + Parameters = new List + { + new OpenApiParameter + { + Name = "Test Parameter", + In = ParameterLocation.Path, + Schema = new OpenApiSchema + { + Title = "title1", + MultipleOf = 3, + Maximum = 42, + ExclusiveMinimum = true, + Minimum = 10, + Default = new OpenApiInteger(15), + Type = "integer", + + Nullable = true, + ExternalDocs = new OpenApiExternalDocs + { + Url = new Uri("http://example.com/externalDocs") + }, + + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + } + } + }, + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/paths/~1test/get/parameters/0/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/paths/~1test/get/parameters/0/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/paths/~1test/get/parameters/0/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/paths/~1test/get/parameters/0/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + + // Differences in request and response + yield return new object[] + { + "Differences in request and response", + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + RequestBody = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + Responses = new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + }, + new OpenApiDocument + { + Paths = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + RequestBody = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + Responses = new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + }, + { + "400", + new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + } + } + } + } + } + } + } + } + }, + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/paths/~1test/get/requestBody/content/application~1xml/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/paths/~1test/get/requestBody/content/application~1xml/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/paths/~1test/get/responses/400", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs index 947f659b7..8659aeeb7 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs @@ -36,13 +36,13 @@ public void OpenApiComparerShouldSucceed( var differences = OpenApiComparer.Compare(source, target).ToList(); differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - for (int i = 0; i < differences.Count(); i++) + for (var i = 0; i < differences.Count(); i++) { differences[i].Pointer.ShouldBeEquivalentTo(expectedDifferences[i].Pointer); differences[i].OpenApiComparedElementType - .ShouldBeEquivalentTo( expectedDifferences[i].OpenApiComparedElementType ); + .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiComparedElementType); differences[i].OpenApiDifferenceOperation - .ShouldBeEquivalentTo( expectedDifferences[i].OpenApiDifferenceOperation ); + .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiDifferenceOperation); } } } diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs new file mode 100644 index 000000000..9496f07c8 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs @@ -0,0 +1,508 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.Tests.Services +{ + [Collection("DefaultSettings")] + public class OpenApiRequestBodyComparerTests + { + private readonly ITestOutputHelper _output; + + private readonly OpenApiDocument _sourceDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + private readonly OpenApiDocument _targetDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + public OpenApiRequestBodyComparerTests(ITestOutputHelper output) + { + _output = output; + } + + public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerShouldSucceed() + { + // Differences in description and Required + yield return new object[] + { + "Differences in description and Required", + new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + new OpenApiRequestBody + { + Description = "udpated description", + Required = false, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/description", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string) + }, + new OpenApiDifference + { + Pointer = "#/required", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool) + } + } + }; + + // Differences in Content + yield return new object[] + { + "Differences in Content", + new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/content/application~1xml", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/content/application~1json", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + + // Null source + yield return new object[] + { + "Null source", + null, + new OpenApiRequestBody + { + Description = "udpated description", + Required = false, + Content = + { + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiRequestBody) + } + } + }; + + // Null target + yield return new object[] + { + "Null target", + new OpenApiRequestBody + { + Description = "udpated description", + Required = false, + Content = + { + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + null, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiRequestBody) + } + } + }; + + // Differences in reference id + yield return new object[] + { + "Differences in reference id", + new OpenApiRequestBody + { + Reference = new OpenApiReference + { + Id = "Id", + Type = ReferenceType.RequestBody + }, + + Description = "description", + Required = true + }, + new OpenApiRequestBody + { + Reference = new OpenApiReference + { + Id = "NewId", + Type = ReferenceType.RequestBody + }, + + Description = "description", + Required = true + }, + new List + { + new OpenApiDifference + { + Pointer = "#/$ref", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiReference) + } + } + }; + + // Differences in schema + yield return new object[] + { + "Differences in schema", + new OpenApiRequestBody + { + Reference = new OpenApiReference + { + Id = "requestBody1", + Type = ReferenceType.RequestBody + }, + + Description = "description", + Required = true + }, + new OpenApiRequestBody + { + Reference = new OpenApiReference + { + Id = "requestBody1", + Type = ReferenceType.RequestBody + }, + + Description = "description", + Required = true + }, + new List + { + new OpenApiDifference + { + Pointer = "#/content/application~1json/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/content/application~1json/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/content/application~1json/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/content/application~1json/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + } + + [Theory] + [MemberData(nameof(GetTestCasesForOpenApiRequestBodyComparerShouldSucceed))] + public void OpenApiRequestBodyComparerShouldSucceed( + string testCaseName, + OpenApiRequestBody source, + OpenApiRequestBody target, + List expectedDifferences) + { + _output.WriteLine(testCaseName); + + var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), _sourceDocument, + _targetDocument); + var comparer = new OpenApiRequestBodyComparer(); + comparer.Compare(source, target, comparisonContext); + + var differences = comparisonContext.OpenApiDifferences.ToList(); + + differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); + + for (var i = 0; i < differences.Count(); i++) + { + differences[i].Pointer.ShouldBeEquivalentTo(expectedDifferences[i].Pointer); + differences[i].OpenApiComparedElementType + .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiComparedElementType); + differences[i].OpenApiDifferenceOperation + .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiDifferenceOperation); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs new file mode 100644 index 000000000..5257740f1 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs @@ -0,0 +1,653 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.Tests.Services +{ + [Collection("DefaultSettings")] + public class OpenApiResponsesComparerTests + { + private readonly ITestOutputHelper _output; + + private readonly OpenApiDocument _sourceDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + Responses = new Dictionary + { + ["responseObject1"] = new OpenApiResponse + { + Description = "description", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["responseObject2"] = new OpenApiResponse + { + Description = "description", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + private readonly OpenApiDocument _targetDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + Responses = new Dictionary + { + ["responseObject1"] = new OpenApiResponse + { + Description = "description", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["responseObject2"] = new OpenApiResponse + { + Description = "description", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + public OpenApiResponsesComparerTests(ITestOutputHelper output) + { + _output = output; + } + + public static IEnumerable GetTestCasesForOpenApiResponsesComparerShouldSucceed() + { + // Differences in description + yield return new object[] + { + "Differences in description", + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "A complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + } + } + }, + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/200/description", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string) + } + } + }; + + // New response code + yield return new object[] + { + "New response code", + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "A complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + }, + new OpenApiResponses + { + { + "400", + new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/400", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/200", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + + // Differences in Content + yield return new object[] + { + "Differences in Content", + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "A complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + }, + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "A complex object array response", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/200/content/application~1json", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/200/content/text~1plain", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + + // Null source + yield return new object[] + { + "Null source", + null, + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(IDictionary) + } + } + }; + + // Null target + yield return new object[] + { + "Null target", + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + }, + null, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(IDictionary) + } + } + }; + + // Differences in reference id + yield return new object[] + { + "Differences in reference id", + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "A complex object array response", + Reference = new OpenApiReference + { + Id = "responseObject1", + Type = ReferenceType.Response + } + } + } + }, + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "A complex object array response", + Reference = new OpenApiReference + { + Id = "responseObject2", + Type = ReferenceType.Response + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/200/$ref", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiReference) + } + } + }; + + // Differences in schema + yield return new object[] + { + "Differences in schema", + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "A complex object array response", + Reference = new OpenApiReference + { + Id = "responseObject1", + Type = ReferenceType.Response + } + } + } + }, + new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "A complex object array response", + Reference = new OpenApiReference + { + Id = "responseObject1", + Type = ReferenceType.Response + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/200/content/application~1json/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/200/content/application~1json/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/200/content/application~1json/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/200/content/application~1json/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + } + + [Theory] + [MemberData(nameof(GetTestCasesForOpenApiResponsesComparerShouldSucceed))] + public void OpenApiResponsesComparerShouldSucceed( + string testCaseName, + OpenApiResponses source, + OpenApiResponses target, + List expectedDifferences) + { + _output.WriteLine(testCaseName); + + var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), _sourceDocument, + _targetDocument); + var comparer = new OpenApiDictionaryComparer(); + comparer.Compare(source, target, comparisonContext); + + var differences = comparisonContext.OpenApiDifferences.ToList(); + + differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); + + for (var i = 0; i < differences.Count(); i++) + { + differences[i].Pointer.ShouldBeEquivalentTo(expectedDifferences[i].Pointer); + differences[i].OpenApiComparedElementType + .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiComparedElementType); + differences[i].OpenApiDifferenceOperation + .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiDifferenceOperation); + } + } + } +} \ No newline at end of file From f59c30833a85cf9fef2d273c5a43fc97c7bd87ab Mon Sep 17 00:00:00 2001 From: Shweta Patil Date: Thu, 16 Aug 2018 14:04:03 -0700 Subject: [PATCH 4/9] More diff work --- .../Services/OpenApiComparerBase.cs | 44 +- .../Services/OpenApiComparerFactory.cs | 17 +- .../Services/OpenApiComponentsComparer.cs | 87 +++ .../Services/OpenApiDocumentComparer.cs | 19 +- .../Services/OpenApiEncodingComparer.cs | 68 ++ .../Services/OpenApiHeaderComparer.cs | 112 ++++ .../Services/OpenApiMediaTypeComparer.cs | 9 +- .../Services/OpenApiOperationComparer.cs | 8 +- .../Services/OpenApiParametersComparer.cs | 10 +- .../Services/OpenApiPathItemComparer.cs | 8 +- .../Services/OpenApiResponseComparer.cs | 8 +- .../Services/OpenApiServerComparer.cs | 61 ++ .../Services/OpenApiServerVariableComparer.cs | 54 ++ .../Services/OpenApiServersComparer.cs | 100 +++ .../Services/OpenApiComparerTests.cs | 15 +- .../Services/OpenApiComponentsTests.cs | 610 ++++++++++++++++++ .../Services/OpenApiEncodingComparerTests.cs | 342 ++++++++++ .../OpenApiRequestBodyComparerTests.cs | 18 +- .../Services/OpenApiResponsesComparerTests.cs | 15 +- .../OpenApiServerVariableComparerTests.cs | 289 +++++++++ .../Services/OpenApiServersComparerTests.cs | 437 +++++++++++++ 21 files changed, 2287 insertions(+), 44 deletions(-) create mode 100644 src/Microsoft.OpenApi/Services/OpenApiComponentsComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiEncodingComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiHeaderComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiServerComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiServerVariableComparer.cs create mode 100644 src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiEncodingComparerTests.cs create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiServerVariableComparerTests.cs create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiServersComparerTests.cs diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs index dd8fa760a..8f9eb721c 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs @@ -64,7 +64,7 @@ internal void Compare(bool? source, bool? target, ComparisonContext comparisonCo comparisonContext.AddOpenApiDifference(new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(bool), + OpenApiComparedElementType = typeof(bool?), SourceValue = source, TargetValue = target, Pointer = comparisonContext.PathString @@ -90,7 +90,47 @@ internal void Compare(decimal? source, decimal? target, ComparisonContext compar comparisonContext.AddOpenApiDifference(new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(decimal), + OpenApiComparedElementType = typeof(decimal?), + SourceValue = source, + TargetValue = target, + Pointer = comparisonContext.PathString + }); + } + } + + /// + /// Compares Enum. + /// + /// The source. + /// The target. + /// The context under which to compare the objects. + internal void Compare(Enum source, Enum target, ComparisonContext comparisonContext) + { + if (source == null && target == null) + { + return; + } + + if (source == null || target == null) + { + comparisonContext.AddOpenApiDifference(new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(TE), + SourceValue = source, + TargetValue = target, + Pointer = comparisonContext.PathString + }); + + return; + } + + if (!source.Equals(target)) + { + comparisonContext.AddOpenApiDifference(new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(T), SourceValue = source, TargetValue = target, Pointer = comparisonContext.PathString diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs b/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs index 780ea0e7c..e10f0aa5f 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs @@ -24,8 +24,23 @@ public class OpenApiComparerFactory {typeof(OpenApiMediaType), new OpenApiMediaTypeComparer()}, {typeof(IDictionary), new OpenApiDictionaryComparer()}, {typeof(IDictionary), new OpenApiDictionaryComparer()}, + {typeof(IDictionary), new OpenApiDictionaryComparer()}, + {typeof(IDictionary), new OpenApiDictionaryComparer()}, + { + typeof(IDictionary), + new OpenApiDictionaryComparer() + }, + {typeof(IDictionary), new OpenApiDictionaryComparer()}, + {typeof(IDictionary), new OpenApiDictionaryComparer()}, + {typeof(IDictionary), new OpenApiDictionaryComparer()}, + {typeof(OpenApiHeader), new OpenApiHeaderComparer()}, {typeof(OpenApiRequestBody), new OpenApiRequestBodyComparer()}, - {typeof(OpenApiResponse), new OpenApiResponseComparer()} + {typeof(OpenApiResponse), new OpenApiResponseComparer()}, + {typeof(OpenApiComponents), new OpenApiComponentsComparer()}, + {typeof(OpenApiEncoding), new OpenApiEncodingComparer()}, + {typeof(IList), new OpenApiServersComparer()}, + {typeof(OpenApiServer), new OpenApiServerComparer()}, + {typeof(OpenApiServerVariable), new OpenApiServerVariableComparer()} }; private readonly Dictionary _typeToComparerMap = new Dictionary(); diff --git a/src/Microsoft.OpenApi/Services/OpenApiComponentsComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiComponentsComparer.cs new file mode 100644 index 000000000..bdb2ed3e5 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiComponentsComparer.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiComponentsComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiComponents sourceComponents, + OpenApiComponents targetComponents, + ComparisonContext comparisonContext) + { + if (sourceComponents == null && targetComponents == null) + { + return; + } + + if (sourceComponents == null || targetComponents == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceComponents, + TargetValue = targetComponents, + OpenApiComparedElementType = typeof(OpenApiComponents), + Pointer = comparisonContext.PathString + }); + + return; + } + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Parameters, + () => comparisonContext + .GetComparer>() + .Compare(sourceComponents.Parameters, targetComponents.Parameters, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.RequestBodies, + () => comparisonContext + .GetComparer>() + .Compare(sourceComponents.RequestBodies, targetComponents.RequestBodies, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Responses, + () => comparisonContext + .GetComparer>() + .Compare(sourceComponents.Responses, targetComponents.Responses, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Schemas, + () => comparisonContext + .GetComparer>() + .Compare(sourceComponents.Schemas, targetComponents.Schemas, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Headers, + () => comparisonContext + .GetComparer>() + .Compare(sourceComponents.Headers, targetComponents.Headers, comparisonContext)); + + // To Do compare Examples + // To Do compare SecuritySchemes + // To Do compare Links + // To Do compare Callbacks + // To Do compare Extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs index 625e7ac46..41e3dc046 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiDocumentComparer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Collections.Generic; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Services @@ -14,7 +15,7 @@ public class OpenApiDocumentComparer : OpenApiComparerBase /// Executes comparision against source and target . /// /// The source. - /// The target + /// The target. /// Context under which to compare the source and target. public override void Compare( OpenApiDocument sourceDocument, @@ -28,9 +29,21 @@ public override void Compare( .GetComparer() .Compare(sourceDocument.Paths, targetDocument.Paths, comparisonContext)); + WalkAndCompare( + comparisonContext, + OpenApiConstants.Components, + () => comparisonContext + .GetComparer() + .Compare(sourceDocument.Components, targetDocument.Components, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Components, + () => comparisonContext + .GetComparer>() + .Compare(sourceDocument.Servers, targetDocument.Servers, comparisonContext)); + // To Do Compare Info - // To Do Compare Servers - // To Do Compare Components // To Do Compare Security Requirements // To Do Compare Tags // To Do Compare External Docs diff --git a/src/Microsoft.OpenApi/Services/OpenApiEncodingComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiEncodingComparer.cs new file mode 100644 index 000000000..9d144b8ac --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiEncodingComparer.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiEncodingComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiEncoding sourceEncoding, + OpenApiEncoding targetEncoding, + ComparisonContext comparisonContext) + { + if (sourceEncoding == null && targetEncoding == null) + { + return; + } + + if (sourceEncoding == null || targetEncoding == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceEncoding, + TargetValue = targetEncoding, + OpenApiComparedElementType = typeof(OpenApiEncoding), + Pointer = comparisonContext.PathString + }); + + return; + } + + WalkAndCompare(comparisonContext, OpenApiConstants.ContentType, + () => Compare(sourceEncoding.ContentType, targetEncoding.ContentType, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Explode, + () => Compare(sourceEncoding.Explode, targetEncoding.Explode, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.AllowReserved, + () => Compare(sourceEncoding.AllowReserved, targetEncoding.AllowReserved, comparisonContext)); + + + WalkAndCompare(comparisonContext, OpenApiConstants.Style, + () => Compare(sourceEncoding.Style, targetEncoding.Style, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Headers, + () => comparisonContext + .GetComparer>() + .Compare(sourceEncoding.Headers, targetEncoding.Headers, comparisonContext)); + + // To Do Compare Extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiHeaderComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiHeaderComparer.cs new file mode 100644 index 000000000..4cdeb5ddf --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiHeaderComparer.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiHeaderComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiHeader sourceHeader, + OpenApiHeader targetHeader, + ComparisonContext comparisonContext) + { + if (sourceHeader == null && targetHeader == null) + { + return; + } + + if (sourceHeader == null || targetHeader == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceHeader, + TargetValue = targetHeader, + OpenApiComparedElementType = typeof(OpenApiHeader), + Pointer = comparisonContext.PathString + }); + + return; + } + + if (sourceHeader.Reference != null + && targetHeader.Reference != null + && sourceHeader.Reference.Id != targetHeader.Reference.Id) + { + WalkAndAddOpenApiDifference( + comparisonContext, + "$ref", + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceHeader.Reference, + TargetValue = targetHeader.Reference, + OpenApiComparedElementType = typeof(OpenApiReference) + }); + + return; + } + + if (sourceHeader.Reference != null) + { + sourceHeader = (OpenApiHeader) comparisonContext.SourceDocument.ResolveReference( + targetHeader.Reference); + } + + if (targetHeader.Reference != null) + { + targetHeader = (OpenApiHeader) comparisonContext.TargetDocument.ResolveReference( + targetHeader.Reference); + } + + WalkAndCompare(comparisonContext, OpenApiConstants.Description, + () => Compare(sourceHeader.Description, targetHeader.Description, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Required, + () => Compare(sourceHeader.Required, targetHeader.Required, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Deprecated, + () => Compare(sourceHeader.Deprecated, targetHeader.Deprecated, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.AllowEmptyValue, + () => Compare(sourceHeader.AllowEmptyValue, targetHeader.AllowEmptyValue, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Explode, + () => Compare(sourceHeader.Explode, targetHeader.Explode, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.AllowReserved, + () => Compare(sourceHeader.AllowReserved, targetHeader.AllowReserved, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Content, + () => comparisonContext + .GetComparer>() + .Compare(sourceHeader.Content, targetHeader.Content, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Schema, + () => comparisonContext + .GetComparer() + .Compare(sourceHeader.Schema, targetHeader.Schema, comparisonContext)); + + // To do compare example + // To do compare examples + // To do compare extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs index 8388afcbd..8a1efdf04 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Collections.Generic; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Services @@ -45,9 +46,15 @@ public override void Compare( .GetComparer() .Compare(sourceMediaType.Schema, targetMediaType.Schema, comparisonContext); + WalkAndCompare( + comparisonContext, + OpenApiConstants.Content, + () => comparisonContext + .GetComparer>() + .Compare(sourceMediaType.Encoding, sourceMediaType.Encoding, comparisonContext)); + // To Do Compare Example // To Do Compare Examples - // To Do Compare Encoding // To Do Compare Extensions } } diff --git a/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs index 28a931665..352d5eb6d 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs @@ -62,10 +62,16 @@ public override void Compare( .GetComparer>() .Compare(sourceOperation?.Responses, targetOperation?.Responses, comparisonContext)); + WalkAndCompare( + comparisonContext, + OpenApiConstants.Components, + () => comparisonContext + .GetComparer>() + .Compare(sourceOperation?.Servers, targetOperation?.Servers, comparisonContext)); + // Compare CallBack // Compare Security Requirements // Compare Extensions - // Compare Servers // Compare External Docs // Compare Tags } diff --git a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs index 0317501a7..7144a8aea 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs @@ -77,15 +77,7 @@ public override void Compare( if (targetParameter == null) { - WalkAndAddOpenApiDifference( - comparisonContext, i.ToString(), - new OpenApiDifference - { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - SourceValue = sourceParameter, - OpenApiComparedElementType = typeof(OpenApiParameter) - }); - return; + continue; } WalkAndCompare( diff --git a/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs index cf5846c11..043bb6049 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs @@ -47,7 +47,13 @@ public override void Compare( .GetComparer>() .Compare(sourcePathItem?.Parameters, targetPathItem?.Parameters, comparisonContext)); - // To Do Compare Servers + WalkAndCompare( + comparisonContext, + OpenApiConstants.Parameters, + () => comparisonContext + .GetComparer>() + .Compare(sourcePathItem?.Servers, targetPathItem?.Servers, comparisonContext)); + // To Do Compare Extensions } } diff --git a/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs index 5f2306b2f..e45a94486 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs @@ -82,7 +82,13 @@ public override void Compare( .GetComparer>() .Compare(sourceResponse.Content, targetResponse.Content, comparisonContext)); - // To Do Compare Headers + WalkAndCompare( + comparisonContext, + OpenApiConstants.Headers, + () => comparisonContext + .GetComparer>() + .Compare(sourceResponse.Headers, targetResponse.Headers, comparisonContext)); + // To Do Compare Link // To Do Compare Extensions } diff --git a/src/Microsoft.OpenApi/Services/OpenApiServerComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiServerComparer.cs new file mode 100644 index 000000000..47245bd71 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiServerComparer.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiServerComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiServer sourceServer, + OpenApiServer targetServer, + ComparisonContext comparisonContext) + { + if (sourceServer == null && targetServer == null) + { + return; + } + + if (sourceServer == null || targetServer == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceServer, + TargetValue = targetServer, + OpenApiComparedElementType = typeof(OpenApiServer), + Pointer = comparisonContext.PathString + }); + + return; + } + + WalkAndCompare(comparisonContext, OpenApiConstants.Description, + () => Compare(sourceServer.Description, targetServer.Description, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Url, + () => Compare(sourceServer.Url, targetServer.Url, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Variables, + () => comparisonContext + .GetComparer>() + .Compare(sourceServer.Variables, sourceServer.Variables, comparisonContext)); + + // To Do compare extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiServerVariableComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiServerVariableComparer.cs new file mode 100644 index 000000000..802994243 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiServerVariableComparer.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of . + /// + public class OpenApiServerVariableComparer : OpenApiComparerBase + { + /// + /// Executes comparision against source and target . + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + OpenApiServerVariable sourceServerVariable, + OpenApiServerVariable targetServerVariable, + ComparisonContext comparisonContext) + { + if (sourceServerVariable == null && targetServerVariable == null) + { + return; + } + + if (sourceServerVariable == null || targetServerVariable == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceServerVariable, + TargetValue = targetServerVariable, + OpenApiComparedElementType = typeof(OpenApiServerVariable), + Pointer = comparisonContext.PathString + }); + + return; + } + + WalkAndCompare(comparisonContext, OpenApiConstants.Description, + () => Compare(sourceServerVariable.Description, targetServerVariable.Description, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Default, + () => Compare(sourceServerVariable.Default, targetServerVariable.Default, comparisonContext)); + + // To Do compare enum + // To Do compare extensions + } + } +} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs new file mode 100644 index 000000000..cb5c99c8e --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Services +{ + /// + /// Defines behavior for comparing properties of + /// where T is. + /// + public class OpenApiServersComparer : OpenApiComparerBase> + { + /// + /// Executes comparision against source and target + /// where T is. + /// + /// The source. + /// The target. + /// Context under which to compare the source and target. + public override void Compare( + IList sourceServers, + IList targetServers, + ComparisonContext comparisonContext) + { + if (sourceServers == null && targetServers == null) + { + return; + } + + if (sourceServers == null || targetServers == null) + { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceServers, + TargetValue = targetServers, + OpenApiComparedElementType = typeof(IList), + Pointer = comparisonContext.PathString + }); + + return; + } + + var newServersInTarget = targetServers.Where( + targetServer => sourceServers.All(sourceServer => sourceServer.Url != targetServer.Url)).ToList(); + + for (var i = 0; i < newServersInTarget?.Count; i++) + { + WalkAndAddOpenApiDifference( + comparisonContext, + i.ToString(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = newServersInTarget[i], + OpenApiComparedElementType = typeof(OpenApiServer) + }); + } + + var removedServers = sourceServers.Where( + sourceServer => targetServers.All(targetServer => sourceServer.Url != targetServer.Url)).ToList(); + + for (var i = 0; i < removedServers.Count; i++) + { + WalkAndAddOpenApiDifference( + comparisonContext, + i.ToString(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = removedServers[i], + OpenApiComparedElementType = typeof(OpenApiServer) + }); + } + + for (var i = 0; i < sourceServers.Count; i++) + { + var sourceServer = sourceServers[i]; + var targetServer = targetServers + .FirstOrDefault(server => server.Url == sourceServer.Url); + + if (targetServer == null) + { + continue; + } + + WalkAndCompare( + comparisonContext, + i.ToString(), + () => comparisonContext + .GetComparer() + .Compare(sourceServer, targetServer, comparisonContext)); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs index 8659aeeb7..d39861ab4 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs @@ -36,14 +36,13 @@ public void OpenApiComparerShouldSucceed( var differences = OpenApiComparer.Compare(source, target).ToList(); differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - for (var i = 0; i < differences.Count(); i++) - { - differences[i].Pointer.ShouldBeEquivalentTo(expectedDifferences[i].Pointer); - differences[i].OpenApiComparedElementType - .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiComparedElementType); - differences[i].OpenApiDifferenceOperation - .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiDifferenceOperation); - } + var notExpectedDifferences = differences.Where( + actualDiff => !expectedDifferences.Any( + expectedDiff => expectedDiff.Pointer == actualDiff.Pointer + && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType + && expectedDiff.OpenApiDifferenceOperation == actualDiff.OpenApiDifferenceOperation)).ToList(); + + notExpectedDifferences.Count.ShouldBeEquivalentTo(0); } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs new file mode 100644 index 000000000..1188150ce --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs @@ -0,0 +1,610 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.Tests.Services +{ + [Collection("DefaultSettings")] + public class OpenApiComponentsTests + { + private readonly ITestOutputHelper _output; + + private readonly OpenApiDocument _sourceDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + private readonly OpenApiDocument _targetDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + public OpenApiComponentsTests(ITestOutputHelper output) + { + _output = output; + } + + public static IEnumerable GetTestCasesForOpenApiComponentsComparerShouldSucceed() + { + // Differences in schema and request body + yield return new object[] + { + "Differences in schema and request body", + new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + }, + new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/requestBodies/requestBody1/content/application~1json/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/requestBodies/requestBody1/content/application~1json/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/requestBodies/requestBody1/content/application~1json/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/requestBodies/requestBody1/content/application~1json/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/schemas/schemaObject1/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/schemas/schemaObject1/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/schemas/schemaObject1/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/schemas/schemaObject1/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/schemas/schemaObject2/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = "#/schemas/schemaObject2/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/schemas/schemaObject2/properties/property6/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/schemas/schemaObject2/properties/property6/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + + // New schema and request body + yield return new object[] + { + "New schema and request body", + new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + } + } + }, + new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/requestBodies/requestBody2", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + }, + new OpenApiDifference + { + Pointer = + "#/schemas/schemaObject2", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair) + } + } + }; + } + + [Theory] + [MemberData(nameof(GetTestCasesForOpenApiComponentsComparerShouldSucceed))] + public void OpenApiComponentsComparerShouldSucceed( + string testCaseName, + OpenApiComponents source, + OpenApiComponents target, + List expectedDifferences) + { + _output.WriteLine(testCaseName); + + var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), _sourceDocument, + _targetDocument); + var comparer = new OpenApiComponentsComparer(); + comparer.Compare(source, target, comparisonContext); + + var differences = comparisonContext.OpenApiDifferences.ToList(); + + differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); + + var notExpectedDifferences = differences.Where( + actualDiff => !expectedDifferences.Any( + expectedDiff => expectedDiff.Pointer == actualDiff.Pointer + && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType + && expectedDiff.OpenApiDifferenceOperation == + actualDiff.OpenApiDifferenceOperation)).ToList(); + + notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiEncodingComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiEncodingComparerTests.cs new file mode 100644 index 000000000..9e7b0cc6f --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiEncodingComparerTests.cs @@ -0,0 +1,342 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.Tests.Services +{ + [Collection("DefaultSettings")] + public class OpenApiEncodingComparerTests + { + private readonly ITestOutputHelper _output; + + private readonly OpenApiDocument _sourceDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + private readonly OpenApiDocument _targetDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + public OpenApiEncodingComparerTests(ITestOutputHelper output) + { + _output = output; + } + + public static IEnumerable GetTestCasesForOpenApiEncodingComparerShouldSucceed() + { + // Differences in ContentType,Style,Explode and AllowReserved + yield return new object[] + { + "Differences in ContentType,Style,Explode and AllowReserved", + new OpenApiEncoding + { + ContentType = "image/png, image/jpeg", + Style = ParameterStyle.Simple, + Explode = true, + AllowReserved = true + }, + new OpenApiEncoding + { + ContentType = "image/jpeg", + Style = ParameterStyle.Form, + Explode = false, + AllowReserved = false + }, + new List + { + new OpenApiDifference + { + Pointer = "#/contentType", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string) + }, + new OpenApiDifference + { + Pointer = "#/style", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(ParameterStyle) + }, + new OpenApiDifference + { + Pointer = "#/explode", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?) + }, + new OpenApiDifference + { + Pointer = "#/allowReserved", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?) + } + } + }; + + // Null source + yield return new object[] + { + "Null source", + null, + new OpenApiEncoding + { + ContentType = "image/jpeg", + Style = ParameterStyle.Form, + Explode = false, + AllowReserved = false + }, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiEncoding) + } + } + }; + + // Null target + yield return new object[] + { + "Null target", + new OpenApiEncoding + { + ContentType = "image/jpeg", + Style = ParameterStyle.Form, + Explode = false, + AllowReserved = false + }, + null, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiEncoding) + } + } + }; + } + + [Theory] + [MemberData(nameof(GetTestCasesForOpenApiEncodingComparerShouldSucceed))] + public void OpenApiEncodingComparerShouldSucceed( + string testCaseName, + OpenApiEncoding source, + OpenApiEncoding target, + List expectedDifferences) + { + _output.WriteLine(testCaseName); + + var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), _sourceDocument, + _targetDocument); + var comparer = new OpenApiEncodingComparer(); + comparer.Compare(source, target, comparisonContext); + + var differences = comparisonContext.OpenApiDifferences.ToList(); + differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); + + var notExpectedDifferences = differences.Where( + actualDiff => !expectedDifferences.Any( + expectedDiff => expectedDiff.Pointer == actualDiff.Pointer + && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType + && expectedDiff.OpenApiDifferenceOperation == + actualDiff.OpenApiDifferenceOperation)).ToList(); + + notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs index 9496f07c8..6ed825999 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs @@ -263,7 +263,7 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho { Pointer = "#/required", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(bool) + OpenApiComparedElementType = typeof(bool?) } } }; @@ -495,14 +495,14 @@ public void OpenApiRequestBodyComparerShouldSucceed( differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - for (var i = 0; i < differences.Count(); i++) - { - differences[i].Pointer.ShouldBeEquivalentTo(expectedDifferences[i].Pointer); - differences[i].OpenApiComparedElementType - .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiComparedElementType); - differences[i].OpenApiDifferenceOperation - .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiDifferenceOperation); - } + var notExpectedDifferences = differences.Where( + actualDiff => !expectedDifferences.Any( + expectedDiff => expectedDiff.Pointer == actualDiff.Pointer + && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType + && expectedDiff.OpenApiDifferenceOperation == + actualDiff.OpenApiDifferenceOperation)).ToList(); + + notExpectedDifferences.Count.ShouldBeEquivalentTo(0); } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs index 5257740f1..0af630eaa 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs @@ -640,14 +640,13 @@ public void OpenApiResponsesComparerShouldSucceed( differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - for (var i = 0; i < differences.Count(); i++) - { - differences[i].Pointer.ShouldBeEquivalentTo(expectedDifferences[i].Pointer); - differences[i].OpenApiComparedElementType - .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiComparedElementType); - differences[i].OpenApiDifferenceOperation - .ShouldBeEquivalentTo(expectedDifferences[i].OpenApiDifferenceOperation); - } + var notExpectedDifferences = differences.Where( + actualDiff => !expectedDifferences.Any( + expectedDiff => expectedDiff.Pointer == actualDiff.Pointer + && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType + && expectedDiff.OpenApiDifferenceOperation == actualDiff.OpenApiDifferenceOperation)).ToList(); + + notExpectedDifferences.Count.ShouldBeEquivalentTo(0); } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiServerVariableComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiServerVariableComparerTests.cs new file mode 100644 index 000000000..c6f5526a5 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiServerVariableComparerTests.cs @@ -0,0 +1,289 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.Tests.Services +{ + [Collection("DefaultSettings")] + public class OpenApiServerVariableComparerTests + { + private readonly ITestOutputHelper _output; + + private readonly OpenApiDocument _sourceDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + private readonly OpenApiDocument _targetDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + public OpenApiServerVariableComparerTests(ITestOutputHelper output) + { + _output = output; + } + + public static IEnumerable GetTestCasesForOpenApiServerVariableComparerShouldSucceed() + { + // Differences in default and description + yield return new object[] + { + "Differences in default and description", + new OpenApiServerVariable + { + Default = "8443", + Enum = new List + { + "8443", + "443" + }, + Description = "test description" + }, + new OpenApiServerVariable + { + Default = "1003", + Enum = new List + { + "8443", + "443" + }, + Description = "test description updated" + }, + new List + { + new OpenApiDifference + { + Pointer = "#/description", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string) + }, + new OpenApiDifference + { + Pointer = "#/default", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string) + } + } + }; + } + + [Theory] + [MemberData(nameof(GetTestCasesForOpenApiServerVariableComparerShouldSucceed))] + public void OpenApiServerVariableComparerShouldSucceed( + string testCaseName, + OpenApiServerVariable source, + OpenApiServerVariable target, + List expectedDifferences) + { + _output.WriteLine(testCaseName); + + var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), _sourceDocument, + _targetDocument); + var comparer = new OpenApiServerVariableComparer(); + comparer.Compare(source, target, comparisonContext); + + var differences = comparisonContext.OpenApiDifferences.ToList(); + differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); + + var notExpectedDifferences = differences.Where( + actualDiff => !expectedDifferences.Any( + expectedDiff => expectedDiff.Pointer == actualDiff.Pointer + && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType + && expectedDiff.OpenApiDifferenceOperation == actualDiff.OpenApiDifferenceOperation)).ToList(); + + notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiServersComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiServersComparerTests.cs new file mode 100644 index 000000000..5977d42b2 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiServersComparerTests.cs @@ -0,0 +1,437 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.Tests.Services +{ + [Collection("DefaultSettings")] + public class OpenApiServersComparerTests + { + private readonly ITestOutputHelper _output; + + private readonly OpenApiDocument _sourceDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + private readonly OpenApiDocument _targetDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + public OpenApiServersComparerTests(ITestOutputHelper output) + { + _output = output; + } + + public static IEnumerable GetTestCasesForOpenApiServersComparerShouldSucceed() + { + // Differences in description + yield return new object[] + { + "Differences in description", + new List + { + new OpenApiServer + { + Description = "description1", + Url = "https://{username}.example.com:{port}/{basePath}", + Variables = new Dictionary + { + ["username"] = new OpenApiServerVariable + { + Default = "unknown", + Description = "variableDescription1" + }, + ["port"] = new OpenApiServerVariable + { + Default = "8443", + Description = "variableDescription2", + Enum = new List + { + "443", + "8443" + } + }, + ["basePath"] = new OpenApiServerVariable + { + Default = "v1" + } + } + } + }, + new List + { + new OpenApiServer + { + Description = "description2", + Url = "https://{username}.example.com:{port}/{basePath}", + Variables = new Dictionary + { + ["username"] = new OpenApiServerVariable + { + Default = "unknown", + Description = "variableDescription1" + }, + ["port"] = new OpenApiServerVariable + { + Default = "8443", + Description = "variableDescription2", + Enum = new List + { + "443", + "8443" + } + }, + ["basePath"] = new OpenApiServerVariable + { + Default = "v1" + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/0/description", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string) + } + } + }; + + // New and Removed server + yield return new object[] + { + "New and Removed server", + new List + { + new OpenApiServer + { + Description = "description1", + Url = "https://{username}.example.com:{port}/{basePath}", + Variables = new Dictionary + { + ["username"] = new OpenApiServerVariable + { + Default = "unknown", + Description = "variableDescription1" + }, + ["port"] = new OpenApiServerVariable + { + Default = "8443", + Description = "variableDescription2", + Enum = new List + { + "443", + "8443" + } + }, + ["basePath"] = new OpenApiServerVariable + { + Default = "v1" + } + } + } + }, + new List + { + new OpenApiServer + { + Description = "description1", + Url = "https://{username}.example.com:{port}/test", + Variables = new Dictionary + { + ["username"] = new OpenApiServerVariable + { + Default = "unknown", + Description = "variableDescription1" + }, + ["port"] = new OpenApiServerVariable + { + Default = "8443", + Description = "variableDescription2", + Enum = new List + { + "443", + "8443" + } + }, + ["basePath"] = new OpenApiServerVariable + { + Default = "v1" + } + } + }, + new OpenApiServer + { + Description = "description3", + Url = "https://{username}.example.com:{port}/{basePath}/test", + Variables = new Dictionary + { + ["username"] = new OpenApiServerVariable + { + Default = "unknown", + Description = "variableDescription1" + }, + ["port"] = new OpenApiServerVariable + { + Default = "8443", + Description = "variableDescription2", + Enum = new List + { + "443", + "8443" + } + }, + ["basePath"] = new OpenApiServerVariable + { + Default = "v1" + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/0", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(OpenApiServer) + }, + new OpenApiDifference + { + Pointer = "#/1", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(OpenApiServer) + }, + new OpenApiDifference + { + Pointer = "#/0", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(OpenApiServer) + } + } + }; + } + + [Theory] + [MemberData(nameof(GetTestCasesForOpenApiServersComparerShouldSucceed))] + public void OpenApiServersComparerShouldSucceed( + string testCaseName, + IList source, + IList target, + List expectedDifferences) + { + _output.WriteLine(testCaseName); + + var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), _sourceDocument, + _targetDocument); + var comparer = new OpenApiServersComparer(); + comparer.Compare(source, target, comparisonContext); + + var differences = comparisonContext.OpenApiDifferences.ToList(); + differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); + + var notExpectedDifferences = differences.Where( + actualDiff => !expectedDifferences.Any( + expectedDiff => expectedDiff.Pointer == actualDiff.Pointer + && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType + && expectedDiff.OpenApiDifferenceOperation == actualDiff.OpenApiDifferenceOperation)).ToList(); + + notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + } + } +} \ No newline at end of file From 91a905d0630869e24cc02045896bbaf2334edd19 Mon Sep 17 00:00:00 2001 From: Shweta Patil Date: Thu, 27 Sep 2018 15:01:26 -0700 Subject: [PATCH 5/9] Fix review comments --- src/Microsoft.OpenApi/Services/OpenApiComparer.cs | 6 +++--- src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs | 10 +++++----- .../Services/OpenApiEncodingComparer.cs | 1 - .../Services/OpenApiHeaderComparer.cs | 4 ++-- .../Services/OpenApiMediaTypeComparer.cs | 2 +- .../Services/OpenApiOperationComparer.cs | 2 +- .../Services/OpenApiParameterComparer.cs | 1 + .../Services/OpenApiParametersComparer.cs | 4 ++-- .../Services/OpenApiPathItemComparer.cs | 2 +- .../Services/OpenApiRequestBodyComparer.cs | 2 +- .../Services/OpenApiResponseComparer.cs | 2 +- .../Services/OpenApiSchemaComparer.cs | 4 ++-- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiComparer.cs index f0b2744cf..818e30274 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparer.cs @@ -26,11 +26,11 @@ public static IEnumerable Compare(OpenApiDocument source, Ope throw Error.ArgumentNull(nameof(target)); } - var comparisionContext = new ComparisonContext(new OpenApiComparerFactory(), source, target); + var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), source, target); - new OpenApiDocumentComparer().Compare(source, target, comparisionContext); + new OpenApiDocumentComparer().Compare(source, target, comparisonContext); - return comparisionContext.OpenApiDifferences; + return comparisonContext.OpenApiDifferences; } } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs index 8f9eb721c..86e918dcf 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs @@ -104,7 +104,7 @@ internal void Compare(decimal? source, decimal? target, ComparisonContext compar /// The source. /// The target. /// The context under which to compare the objects. - internal void Compare(Enum source, Enum target, ComparisonContext comparisonContext) + internal void Compare(Enum source, Enum target, ComparisonContext comparisonContext) { if (source == null && target == null) { @@ -116,7 +116,7 @@ internal void Compare(Enum source, Enum target, ComparisonContext comparison comparisonContext.AddOpenApiDifference(new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(TE), + OpenApiComparedElementType = typeof(TEnum), SourceValue = source, TargetValue = target, Pointer = comparisonContext.PathString @@ -130,7 +130,7 @@ internal void Compare(Enum source, Enum target, ComparisonContext comparison comparisonContext.AddOpenApiDifference(new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(T), + OpenApiComparedElementType = typeof(TEnum), SourceValue = source, TargetValue = target, Pointer = comparisonContext.PathString @@ -149,7 +149,7 @@ internal void WalkAndAddOpenApiDifference( string segment, OpenApiDifference openApiDifference) { - comparisonContext.Enter(segment.Replace("/", "~1")); + comparisonContext.Enter(segment.Replace("/", "~1").Replace("~", "~0")); openApiDifference.Pointer = comparisonContext.PathString; comparisonContext.AddOpenApiDifference(openApiDifference); comparisonContext.Exit(); @@ -166,7 +166,7 @@ protected virtual void WalkAndCompare( string segment, Action compare) { - comparisonContext.Enter(segment.Replace("/", "~1")); + comparisonContext.Enter(segment.Replace("/", "~1").Replace("~", "~0")); compare(); comparisonContext.Exit(); } diff --git a/src/Microsoft.OpenApi/Services/OpenApiEncodingComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiEncodingComparer.cs index 9d144b8ac..923ab1420 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiEncodingComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiEncodingComparer.cs @@ -51,7 +51,6 @@ public override void Compare( WalkAndCompare(comparisonContext, OpenApiConstants.AllowReserved, () => Compare(sourceEncoding.AllowReserved, targetEncoding.AllowReserved, comparisonContext)); - WalkAndCompare(comparisonContext, OpenApiConstants.Style, () => Compare(sourceEncoding.Style, targetEncoding.Style, comparisonContext)); diff --git a/src/Microsoft.OpenApi/Services/OpenApiHeaderComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiHeaderComparer.cs index 4cdeb5ddf..b57ff4071 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiHeaderComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiHeaderComparer.cs @@ -48,7 +48,7 @@ public override void Compare( { WalkAndAddOpenApiDifference( comparisonContext, - "$ref", + OpenApiConstants.DollarRef, new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, @@ -63,7 +63,7 @@ public override void Compare( if (sourceHeader.Reference != null) { sourceHeader = (OpenApiHeader) comparisonContext.SourceDocument.ResolveReference( - targetHeader.Reference); + sourceHeader.Reference); } if (targetHeader.Reference != null) diff --git a/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs index 8a1efdf04..3eeac4356 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs @@ -48,7 +48,7 @@ public override void Compare( WalkAndCompare( comparisonContext, - OpenApiConstants.Content, + OpenApiConstants.Encoding, () => comparisonContext .GetComparer>() .Compare(sourceMediaType.Encoding, sourceMediaType.Encoding, comparisonContext)); diff --git a/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs index 352d5eb6d..98879e013 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs @@ -64,7 +64,7 @@ public override void Compare( WalkAndCompare( comparisonContext, - OpenApiConstants.Components, + OpenApiConstants.Servers, () => comparisonContext .GetComparer>() .Compare(sourceOperation?.Servers, targetOperation?.Servers, comparisonContext)); diff --git a/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs index ca4241cf6..449c797bf 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs @@ -54,6 +54,7 @@ public override void Compare( .GetComparer>() .Compare(sourceParameter.Content, targetParameter.Content, comparisonContext)); + // To Do Add compare for reference object // To Do Compare Examples // To Do Compare parameter as IOpenApiExtensible } diff --git a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs index 7144a8aea..40c868bce 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs @@ -43,7 +43,7 @@ public override void Compare( { WalkAndAddOpenApiDifference( comparisonContext, - i.ToString(), + targetParameters.IndexOf(newParametersInTarget[i]).ToString(), new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, @@ -60,7 +60,7 @@ public override void Compare( { WalkAndAddOpenApiDifference( comparisonContext, - i.ToString(), + sourceParameters.IndexOf(removedParameters[i]).ToString(), new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, diff --git a/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs index 043bb6049..84c590eee 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs @@ -49,7 +49,7 @@ public override void Compare( WalkAndCompare( comparisonContext, - OpenApiConstants.Parameters, + OpenApiConstants.Servers, () => comparisonContext .GetComparer>() .Compare(sourcePathItem?.Servers, targetPathItem?.Servers, comparisonContext)); diff --git a/src/Microsoft.OpenApi/Services/OpenApiRequestBodyComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiRequestBodyComparer.cs index af8353f32..880c654cf 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiRequestBodyComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiRequestBodyComparer.cs @@ -48,7 +48,7 @@ public override void Compare( { WalkAndAddOpenApiDifference( comparisonContext, - "$ref", + OpenApiConstants.DollarRef, new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, diff --git a/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs index e45a94486..805ad2743 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs @@ -48,7 +48,7 @@ public override void Compare( { WalkAndAddOpenApiDifference( comparisonContext, - "$ref", + OpenApiConstants.DollarRef, new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, diff --git a/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs index 5675bbb9d..8466303b4 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs @@ -109,7 +109,7 @@ public override void Compare( { WalkAndCompare( comparisonContext, - "items", + OpenApiConstants.Items, () => comparisonContext .GetComparer() .Compare(sourceSchema.Items, targetSchema.Items, comparisonContext)); @@ -121,7 +121,7 @@ public override void Compare( { WalkAndAddOpenApiDifference( comparisonContext, - "$ref", + OpenApiConstants.DollarRef, new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, From 73d1208ad5749c0bcfb68d5b4735ec78a73b7f7b Mon Sep 17 00:00:00 2001 From: Shweta Patil Date: Mon, 1 Oct 2018 12:01:59 -0700 Subject: [PATCH 6/9] Fix per review comments --- .../Services/OpenApiComparerBase.cs | 4 +- .../Services/OpenApiParametersComparer.cs | 44 ++- .../Services/OpenApiSchemaComparer.cs | 2 +- .../Services/OpenApiServersComparer.cs | 29 +- .../Services/OpenApiComparerTestCases.cs | 373 ++++++++++++++++-- .../Services/OpenApiComparerTests.cs | 8 +- .../Services/OpenApiComponentsTests.cs | 137 +++++-- .../Services/OpenApiEncodingComparerTests.cs | 45 ++- .../OpenApiRequestBodyComparerTests.cs | 118 +++++- .../Services/OpenApiResponsesComparerTests.cs | 205 +++++++++- .../OpenApiServerVariableComparerTests.cs | 16 +- .../Services/OpenApiServersComparerTests.cs | 102 ++++- 12 files changed, 924 insertions(+), 159 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs index 86e918dcf..d08b5e644 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparerBase.cs @@ -149,7 +149,7 @@ internal void WalkAndAddOpenApiDifference( string segment, OpenApiDifference openApiDifference) { - comparisonContext.Enter(segment.Replace("/", "~1").Replace("~", "~0")); + comparisonContext.Enter(segment.Replace("~", "~0").Replace("/", "~1")); openApiDifference.Pointer = comparisonContext.PathString; comparisonContext.AddOpenApiDifference(openApiDifference); comparisonContext.Exit(); @@ -166,7 +166,7 @@ protected virtual void WalkAndCompare( string segment, Action compare) { - comparisonContext.Enter(segment.Replace("/", "~1").Replace("~", "~0")); + comparisonContext.Enter(segment.Replace("~", "~0").Replace("/", "~1")); compare(); comparisonContext.Exit(); } diff --git a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs index 40c868bce..59dece580 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs @@ -35,28 +35,26 @@ public override void Compare( return; } - var newParametersInTarget = targetParameters?.Where( - targetParam => !sourceParameters.Any( - sourceParam => sourceParam.Name == targetParam.Name && sourceParam.In == targetParam.In)).ToList(); - - for (var i = 0; i < newParametersInTarget?.Count; i++) + if (sourceParameters == null || targetParameters == null) { - WalkAndAddOpenApiDifference( - comparisonContext, - targetParameters.IndexOf(newParametersInTarget[i]).ToString(), + comparisonContext.AddOpenApiDifference( new OpenApiDifference { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - TargetValue = targetParameters[i], - OpenApiComparedElementType = typeof(OpenApiParameter) + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceParameters, + TargetValue = targetParameters, + OpenApiComparedElementType = typeof(OpenApiParameter), + Pointer = comparisonContext.PathString }); + + return; } var removedParameters = sourceParameters?.Where( sourceParam => !targetParameters.Any( targetParam => sourceParam.Name == targetParam.Name && sourceParam.In == targetParam.In)).ToList(); - for (var i = 0; i < removedParameters.Count; i++) + for (var i = removedParameters.Count - 1; i >= 0; i--) { WalkAndAddOpenApiDifference( comparisonContext, @@ -69,9 +67,25 @@ public override void Compare( }); } - for (var i = 0; i < sourceParameters.Count; i++) + var newParametersInTarget = targetParameters?.Where( + targetParam => !sourceParameters.Any( + sourceParam => sourceParam.Name == targetParam.Name && sourceParam.In == targetParam.In)).ToList(); + + foreach (var newParameterInTarget in newParametersInTarget) + { + WalkAndAddOpenApiDifference( + comparisonContext, + targetParameters.IndexOf(newParameterInTarget).ToString(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = newParameterInTarget, + OpenApiComparedElementType = typeof(OpenApiParameter) + }); + } + + foreach (var sourceParameter in sourceParameters) { - var sourceParameter = sourceParameters[i]; var targetParameter = targetParameters .FirstOrDefault(param => param.Name == sourceParameter.Name && param.In == sourceParameter.In); @@ -82,7 +96,7 @@ public override void Compare( WalkAndCompare( comparisonContext, - i.ToString(), + targetParameters.IndexOf(targetParameter).ToString(), () => comparisonContext .GetComparer() .Compare(sourceParameter, targetParameter, comparisonContext)); diff --git a/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs index 8466303b4..e320854fd 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiSchemaComparer.cs @@ -194,7 +194,7 @@ public override void Compare( new OpenApiDifference { OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - TargetValue = sourceSchemaProperty, + SourceValue = sourceSchemaProperty, OpenApiComparedElementType = typeof(KeyValuePair) }); } diff --git a/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs index cb5c99c8e..5ffabc8d9 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs @@ -45,41 +45,40 @@ public override void Compare( return; } - var newServersInTarget = targetServers.Where( - targetServer => sourceServers.All(sourceServer => sourceServer.Url != targetServer.Url)).ToList(); + var removedServers = sourceServers.Where( + sourceServer => targetServers.All(targetServer => sourceServer.Url != targetServer.Url)).ToList(); - for (var i = 0; i < newServersInTarget?.Count; i++) + for (var i = removedServers.Count - 1; i >= 0; i--) { WalkAndAddOpenApiDifference( comparisonContext, - i.ToString(), + removedServers.IndexOf(removedServers[i]).ToString(), new OpenApiDifference { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - TargetValue = newServersInTarget[i], + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = removedServers[i], OpenApiComparedElementType = typeof(OpenApiServer) }); } - var removedServers = sourceServers.Where( - sourceServer => targetServers.All(targetServer => sourceServer.Url != targetServer.Url)).ToList(); + var newServersInTarget = targetServers.Where( + targetServer => sourceServers.All(sourceServer => sourceServer.Url != targetServer.Url)).ToList(); - for (var i = 0; i < removedServers.Count; i++) + foreach (var newServerInTarget in newServersInTarget) { WalkAndAddOpenApiDifference( comparisonContext, - i.ToString(), + targetServers.IndexOf(newServerInTarget).ToString(), new OpenApiDifference { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - SourceValue = removedServers[i], + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = newServerInTarget, OpenApiComparedElementType = typeof(OpenApiServer) }); } - for (var i = 0; i < sourceServers.Count; i++) + foreach (var sourceServer in sourceServers) { - var sourceServer = sourceServers[i]; var targetServer = targetServers .FirstOrDefault(server => server.Url == sourceServer.Url); @@ -90,7 +89,7 @@ public override void Compare( WalkAndCompare( comparisonContext, - i.ToString(), + targetServers.IndexOf(targetServer).ToString(), () => comparisonContext .GetComparer() .Compare(sourceServer, targetServer, comparisonContext)); diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs index 9280c1a93..5242bd769 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs @@ -61,13 +61,37 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { Pointer = "#/paths/~1newPath", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(OpenApiPathItem) + OpenApiComparedElementType = typeof(OpenApiPathItem), + SourceValue = null, + TargetValue = new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } }, new OpenApiDifference { Pointer = "#/paths/~1test", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(OpenApiPathItem) + OpenApiComparedElementType = typeof(OpenApiPathItem), + TargetValue = null, + SourceValue = new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } } } }; @@ -126,13 +150,21 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { Pointer = "#/paths/~1test/patch", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = + new KeyValuePair(OperationType.Patch, + new OpenApiOperation()) }, new OpenApiDifference { Pointer = "#/paths/~1test/post", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = null, + SourceValue = + new KeyValuePair(OperationType.Post, + new OpenApiOperation()) } } }; @@ -167,7 +199,24 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { Pointer = "#/paths", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(OpenApiPaths) + OpenApiComparedElementType = typeof(OpenApiPaths), + SourceValue = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } + } + }, + TargetValue = null } } }; @@ -202,7 +251,24 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { Pointer = "#/paths", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(OpenApiPaths) + OpenApiComparedElementType = typeof(OpenApiPaths), + SourceValue = null, + TargetValue = new OpenApiPaths + { + { + "/newPath", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } + } + } } } }; @@ -253,13 +319,20 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { Pointer = "#/paths/~1test/get", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = null, + SourceValue = + new KeyValuePair(OperationType.Get, new OpenApiOperation()) }, new OpenApiDifference { Pointer = "#/paths/~1test/post", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = null, + SourceValue = + new KeyValuePair(OperationType.Post, + new OpenApiOperation()) } } }; @@ -310,13 +383,20 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { Pointer = "#/paths/~1test/get", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = + new KeyValuePair(OperationType.Get, new OpenApiOperation()) }, new OpenApiDifference { Pointer = "#/paths/~1test/patch", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = + new KeyValuePair(OperationType.Patch, + new OpenApiOperation()) } } }; @@ -426,13 +506,17 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { Pointer = "#/paths/~1test/summary", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + SourceValue = "test", + TargetValue = "updated" }, new OpenApiDifference { Pointer = "#/paths/~1test/description", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + SourceValue = "test", + TargetValue = "updated" } } }; @@ -653,27 +737,127 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { Pointer = "#/paths/~1test/get/parameters/0/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/paths/~1test/get/parameters/0/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/paths/~1test/get/parameters/0/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/paths/~1test/get/parameters/0/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = "#/components/schemas/schemaObject1/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = "#/components/schemas/schemaObject1/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = + "#/components/schemas/schemaObject1/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = + "#/components/schemas/schemaObject1/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = + "#/components/schemas/schemaObject2/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = + "#/components/schemas/schemaObject2/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null } } }; @@ -945,61 +1129,200 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/paths/~1test/get/responses/400", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("400", new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }) }, new OpenApiDifference { Pointer = "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = "#/components/schemas/schemaObject1/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = "#/components/schemas/schemaObject1/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = + "#/components/schemas/schemaObject1/properties/property6/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = + "#/components/schemas/schemaObject1/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = + "#/components/schemas/schemaObject2/properties/property6/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = + "#/components/schemas/schemaObject2/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null } } }; diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs index d39861ab4..306a40abe 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs @@ -36,13 +36,7 @@ public void OpenApiComparerShouldSucceed( var differences = OpenApiComparer.Compare(source, target).ToList(); differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - var notExpectedDifferences = differences.Where( - actualDiff => !expectedDifferences.Any( - expectedDiff => expectedDiff.Pointer == actualDiff.Pointer - && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType - && expectedDiff.OpenApiDifferenceOperation == actualDiff.OpenApiDifferenceOperation)).ToList(); - - notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + differences.ShouldBeEquivalentTo(expectedDifferences); } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs index 1188150ce..66defde0d 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs @@ -375,79 +375,151 @@ public static IEnumerable GetTestCasesForOpenApiComponentsComparerShou { Pointer = "#/requestBodies/requestBody1/content/application~1json/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/requestBodies/requestBody1/content/application~1json/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/requestBodies/requestBody1/content/application~1json/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/requestBodies/requestBody1/content/application~1json/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/schemas/schemaObject1/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/schemas/schemaObject1/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/schemas/schemaObject1/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/schemas/schemaObject1/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/schemas/schemaObject2/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/schemas/schemaObject2/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/schemas/schemaObject2/properties/property6/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/schemas/schemaObject2/properties/property6/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null } } }; @@ -565,14 +637,42 @@ public static IEnumerable GetTestCasesForOpenApiComponentsComparerShou { Pointer = "#/requestBodies/requestBody2", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("requestBody2", + new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }) }, new OpenApiDifference { Pointer = "#/schemas/schemaObject2", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("schemaObject2", new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + } + } + }) } } }; @@ -597,14 +697,7 @@ public void OpenApiComponentsComparerShouldSucceed( differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - var notExpectedDifferences = differences.Where( - actualDiff => !expectedDifferences.Any( - expectedDiff => expectedDiff.Pointer == actualDiff.Pointer - && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType - && expectedDiff.OpenApiDifferenceOperation == - actualDiff.OpenApiDifferenceOperation)).ToList(); - - notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + differences.ShouldBeEquivalentTo(expectedDifferences); } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiEncodingComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiEncodingComparerTests.cs index 9e7b0cc6f..54057859d 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiEncodingComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiEncodingComparerTests.cs @@ -241,25 +241,33 @@ public static IEnumerable GetTestCasesForOpenApiEncodingComparerShould { Pointer = "#/contentType", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + TargetValue = "image/jpeg", + SourceValue = "image/png, image/jpeg" }, new OpenApiDifference { Pointer = "#/style", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(ParameterStyle) + OpenApiComparedElementType = typeof(ParameterStyle), + TargetValue = ParameterStyle.Form, + SourceValue = ParameterStyle.Simple }, new OpenApiDifference { Pointer = "#/explode", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(bool?) + OpenApiComparedElementType = typeof(bool?), + TargetValue = false, + SourceValue = true }, new OpenApiDifference { Pointer = "#/allowReserved", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(bool?) + OpenApiComparedElementType = typeof(bool?), + TargetValue = false, + SourceValue = true } } }; @@ -282,7 +290,15 @@ public static IEnumerable GetTestCasesForOpenApiEncodingComparerShould { Pointer = "#/", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(OpenApiEncoding) + OpenApiComparedElementType = typeof(OpenApiEncoding), + SourceValue = null, + TargetValue = new OpenApiEncoding + { + ContentType = "image/jpeg", + Style = ParameterStyle.Form, + Explode = false, + AllowReserved = false + } } } }; @@ -305,7 +321,15 @@ public static IEnumerable GetTestCasesForOpenApiEncodingComparerShould { Pointer = "#/", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(OpenApiEncoding) + OpenApiComparedElementType = typeof(OpenApiEncoding), + TargetValue = null, + SourceValue = new OpenApiEncoding + { + ContentType = "image/jpeg", + Style = ParameterStyle.Form, + Explode = false, + AllowReserved = false + } } } }; @@ -329,14 +353,7 @@ public void OpenApiEncodingComparerShouldSucceed( var differences = comparisonContext.OpenApiDifferences.ToList(); differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - var notExpectedDifferences = differences.Where( - actualDiff => !expectedDifferences.Any( - expectedDiff => expectedDiff.Pointer == actualDiff.Pointer - && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType - && expectedDiff.OpenApiDifferenceOperation == - actualDiff.OpenApiDifferenceOperation)).ToList(); - - notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + differences.ShouldBeEquivalentTo(expectedDifferences); } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs index 6ed825999..2c1b9b596 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs @@ -257,13 +257,17 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho { Pointer = "#/description", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + TargetValue = "udpated description", + SourceValue = "description" }, new OpenApiDifference { Pointer = "#/required", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(bool?) + OpenApiComparedElementType = typeof(bool?), + TargetValue = false, + SourceValue = true } } }; @@ -308,13 +312,30 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho { Pointer = "#/content/application~1xml", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("application/xml", new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + }) }, new OpenApiDifference { Pointer = "#/content/application~1json", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = null, + SourceValue = new KeyValuePair("application/json", + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + }) } } }; @@ -345,7 +366,23 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho { Pointer = "#/", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(OpenApiRequestBody) + OpenApiComparedElementType = typeof(OpenApiRequestBody), + SourceValue = null, + TargetValue = new OpenApiRequestBody + { + Description = "udpated description", + Required = false, + Content = + { + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + } } } }; @@ -376,7 +413,23 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho { Pointer = "#/", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(OpenApiRequestBody) + OpenApiComparedElementType = typeof(OpenApiRequestBody), + SourceValue = new OpenApiRequestBody + { + Description = "udpated description", + Required = false, + Content = + { + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + TargetValue = null } } }; @@ -413,7 +466,17 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho { Pointer = "#/$ref", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(OpenApiReference) + OpenApiComparedElementType = typeof(OpenApiReference), + TargetValue = new OpenApiReference + { + Id = "NewId", + Type = ReferenceType.RequestBody + }, + SourceValue = new OpenApiReference + { + Id = "Id", + Type = ReferenceType.RequestBody + } } } }; @@ -450,27 +513,51 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho { Pointer = "#/content/application~1json/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/content/application~1json/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/content/application~1json/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/content/application~1json/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null } } }; @@ -495,14 +582,7 @@ public void OpenApiRequestBodyComparerShouldSucceed( differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - var notExpectedDifferences = differences.Where( - actualDiff => !expectedDifferences.Any( - expectedDiff => expectedDiff.Pointer == actualDiff.Pointer - && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType - && expectedDiff.OpenApiDifferenceOperation == - actualDiff.OpenApiDifferenceOperation)).ToList(); - - notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + differences.ShouldBeEquivalentTo(expectedDifferences); } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs index 0af630eaa..cc8c4f9c1 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs @@ -263,7 +263,9 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul { Pointer = "#/200/description", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + SourceValue = "A complex object array response", + TargetValue = "An updated complex object array response" } } }; @@ -334,13 +336,59 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul { Pointer = "#/400", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("400", new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + }) }, new OpenApiDifference { Pointer = "#/200", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = null, + SourceValue = new KeyValuePair("200", new OpenApiResponse + { + Description = "A complex object array response", + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + }) } } }; @@ -411,13 +459,46 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul { Pointer = "#/200/content/application~1json", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("application/json", + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + }) }, new OpenApiDifference { Pointer = "#/200/content/text~1plain", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = null, + SourceValue = new KeyValuePair("text/plain", new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + }) } } }; @@ -461,7 +542,36 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul { Pointer = "#/", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(IDictionary) + OpenApiComparedElementType = typeof(IDictionary), + SourceValue = null, + TargetValue = new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + } } } }; @@ -505,7 +615,36 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul { Pointer = "#/", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(IDictionary) + OpenApiComparedElementType = typeof(IDictionary), + TargetValue = null, + SourceValue = new OpenApiResponses + { + { + "200", + new OpenApiResponse + { + Description = "An updated complex object array response", + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + } + } + } + } } } }; @@ -550,7 +689,17 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul { Pointer = "#/200/$ref", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(OpenApiReference) + OpenApiComparedElementType = typeof(OpenApiReference), + SourceValue = new OpenApiReference + { + Id = "responseObject1", + Type = ReferenceType.Response + }, + TargetValue = new OpenApiReference + { + Id = "responseObject2", + Type = ReferenceType.Response + } } } }; @@ -595,27 +744,51 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul { Pointer = "#/200/content/application~1json/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/200/content/application~1json/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null }, new OpenApiDifference { Pointer = "#/200/content/application~1json/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) }, new OpenApiDifference { Pointer = "#/200/content/application~1json/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null } } }; @@ -640,13 +813,7 @@ public void OpenApiResponsesComparerShouldSucceed( differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - var notExpectedDifferences = differences.Where( - actualDiff => !expectedDifferences.Any( - expectedDiff => expectedDiff.Pointer == actualDiff.Pointer - && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType - && expectedDiff.OpenApiDifferenceOperation == actualDiff.OpenApiDifferenceOperation)).ToList(); - - notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + differences.ShouldBeEquivalentTo(expectedDifferences); } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiServerVariableComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiServerVariableComparerTests.cs index c6f5526a5..0f689dca7 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiServerVariableComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiServerVariableComparerTests.cs @@ -247,13 +247,17 @@ public static IEnumerable GetTestCasesForOpenApiServerVariableComparer { Pointer = "#/description", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + SourceValue = "test description", + TargetValue = "test description updated" }, new OpenApiDifference { Pointer = "#/default", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + SourceValue = "8443", + TargetValue = "1003" } } }; @@ -277,13 +281,7 @@ public void OpenApiServerVariableComparerShouldSucceed( var differences = comparisonContext.OpenApiDifferences.ToList(); differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - var notExpectedDifferences = differences.Where( - actualDiff => !expectedDifferences.Any( - expectedDiff => expectedDiff.Pointer == actualDiff.Pointer - && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType - && expectedDiff.OpenApiDifferenceOperation == actualDiff.OpenApiDifferenceOperation)).ToList(); - - notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + differences.ShouldBeEquivalentTo(expectedDifferences); } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiServersComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiServersComparerTests.cs index 5977d42b2..2719d2661 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiServersComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiServersComparerTests.cs @@ -287,7 +287,9 @@ public static IEnumerable GetTestCasesForOpenApiServersComparerShouldS { Pointer = "#/0/description", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + SourceValue = "description1", + TargetValue = "description2" } } }; @@ -389,19 +391,103 @@ public static IEnumerable GetTestCasesForOpenApiServersComparerShouldS { Pointer = "#/0", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(OpenApiServer) + OpenApiComparedElementType = typeof(OpenApiServer), + SourceValue = null, + TargetValue = new OpenApiServer + { + Description = "description1", + Url = "https://{username}.example.com:{port}/test", + Variables = new Dictionary + { + ["username"] = new OpenApiServerVariable + { + Default = "unknown", + Description = "variableDescription1" + }, + ["port"] = new OpenApiServerVariable + { + Default = "8443", + Description = "variableDescription2", + Enum = new List + { + "443", + "8443" + } + }, + ["basePath"] = new OpenApiServerVariable + { + Default = "v1" + } + } + } }, new OpenApiDifference { Pointer = "#/1", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(OpenApiServer) + OpenApiComparedElementType = typeof(OpenApiServer), + SourceValue = null, + TargetValue = new OpenApiServer + { + Description = "description3", + Url = "https://{username}.example.com:{port}/{basePath}/test", + Variables = new Dictionary + { + ["username"] = new OpenApiServerVariable + { + Default = "unknown", + Description = "variableDescription1" + }, + ["port"] = new OpenApiServerVariable + { + Default = "8443", + Description = "variableDescription2", + Enum = new List + { + "443", + "8443" + } + }, + ["basePath"] = new OpenApiServerVariable + { + Default = "v1" + } + } + } }, new OpenApiDifference { Pointer = "#/0", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(OpenApiServer) + OpenApiComparedElementType = typeof(OpenApiServer), + TargetValue = null, + SourceValue = new OpenApiServer + { + Description = "description1", + Url = "https://{username}.example.com:{port}/{basePath}", + Variables = new Dictionary + { + ["username"] = new OpenApiServerVariable + { + Default = "unknown", + Description = "variableDescription1" + }, + ["port"] = new OpenApiServerVariable + { + Default = "8443", + Description = "variableDescription2", + Enum = new List + { + "443", + "8443" + } + }, + ["basePath"] = new OpenApiServerVariable + { + Default = "v1" + } + } + } } } }; @@ -425,13 +511,7 @@ public void OpenApiServersComparerShouldSucceed( var differences = comparisonContext.OpenApiDifferences.ToList(); differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); - var notExpectedDifferences = differences.Where( - actualDiff => !expectedDifferences.Any( - expectedDiff => expectedDiff.Pointer == actualDiff.Pointer - && expectedDiff.OpenApiComparedElementType == actualDiff.OpenApiComparedElementType - && expectedDiff.OpenApiDifferenceOperation == actualDiff.OpenApiDifferenceOperation)).ToList(); - - notExpectedDifferences.Count.ShouldBeEquivalentTo(0); + differences.ShouldBeEquivalentTo(expectedDifferences); } } } \ No newline at end of file From 497d8a1c4e053953b17182ef1140407b1466cb77 Mon Sep 17 00:00:00 2001 From: Shweta Patil Date: Mon, 1 Oct 2018 15:09:05 -0700 Subject: [PATCH 7/9] Added more tests --- .../Services/OpenApiParameterComparer.cs | 39 +- .../Services/OpenApiParametersComparer.cs | 7 +- .../Services/OpenApiServersComparer.cs | 2 +- .../Services/OpenApiParameterComparerTests.cs | 478 ++++++++++++++++++ .../OpenApiParametersComparerTests.cs | 432 ++++++++++++++++ 5 files changed, 946 insertions(+), 12 deletions(-) create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiParameterComparerTests.cs create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiParametersComparerTests.cs diff --git a/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs index 449c797bf..53d3e6619 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiParameterComparer.cs @@ -42,11 +42,6 @@ public override void Compare( return; } - comparisonContext.GetComparer().Compare( - sourceParameter?.Schema, - targetParameter?.Schema, - comparisonContext); - WalkAndCompare( comparisonContext, OpenApiConstants.Content, @@ -54,6 +49,40 @@ public override void Compare( .GetComparer>() .Compare(sourceParameter.Content, targetParameter.Content, comparisonContext)); + WalkAndCompare(comparisonContext, OpenApiConstants.Description, + () => Compare(sourceParameter.Description, targetParameter.Description, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Required, + () => Compare(sourceParameter.Required, targetParameter.Required, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Name, + () => Compare(sourceParameter.Name, targetParameter.Name, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Deprecated, + () => Compare(sourceParameter.Deprecated, targetParameter.Deprecated, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.AllowEmptyValue, + () => Compare(sourceParameter.AllowEmptyValue, targetParameter.AllowEmptyValue, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Explode, + () => Compare(sourceParameter.Explode, targetParameter.Explode, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.AllowReserved, + () => Compare(sourceParameter.AllowReserved, targetParameter.AllowReserved, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.Style, + () => Compare(sourceParameter.Style, targetParameter.Style, comparisonContext)); + + WalkAndCompare(comparisonContext, OpenApiConstants.In, + () => Compare(sourceParameter.In, targetParameter.In, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Schema, + () => comparisonContext + .GetComparer() + .Compare(sourceParameter.Schema, targetParameter.Schema, comparisonContext)); + // To Do Add compare for reference object // To Do Compare Examples // To Do Compare parameter as IOpenApiExtensible diff --git a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs index 59dece580..f6ad885ef 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs @@ -30,11 +30,6 @@ public override void Compare( return; } - if (!sourceParameters.Any() && !targetParameters.Any()) - { - return; - } - if (sourceParameters == null || targetParameters == null) { comparisonContext.AddOpenApiDifference( @@ -43,7 +38,7 @@ public override void Compare( OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, SourceValue = sourceParameters, TargetValue = targetParameters, - OpenApiComparedElementType = typeof(OpenApiParameter), + OpenApiComparedElementType = typeof(IList), Pointer = comparisonContext.PathString }); diff --git a/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs index 5ffabc8d9..00be2544e 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs @@ -38,7 +38,7 @@ public override void Compare( OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, SourceValue = sourceServers, TargetValue = targetServers, - OpenApiComparedElementType = typeof(IList), + OpenApiComparedElementType = typeof(IList), Pointer = comparisonContext.PathString }); diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiParameterComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiParameterComparerTests.cs new file mode 100644 index 000000000..4bcc4df02 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiParameterComparerTests.cs @@ -0,0 +1,478 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.Tests.Services +{ + [Collection("DefaultSettings")] + public class OpenApiParameterComparerTests + { + private readonly ITestOutputHelper _output; + + private readonly OpenApiDocument _sourceDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + private readonly OpenApiDocument _targetDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + public OpenApiParameterComparerTests(ITestOutputHelper output) + { + _output = output; + } + + public static IEnumerable GetTestCasesForOpenApiParameterComparerShouldSucceed() + { + // Source and Target are null + yield return new object[] + { + "Source and Target are null", + null, + null, + new List() + }; + + // Source is null + yield return new object[] + { + "Source is null", + null, + new OpenApiParameter + { + Name = "pathParam", + In = ParameterLocation.Path + }, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiParameter), + SourceValue = null, + TargetValue = new OpenApiParameter + { + Name = "pathParam", + In = ParameterLocation.Path + } + } + } + }; + + // Target is null + yield return new object[] + { + "Target is null", + new OpenApiParameter + { + Name = "pathParam", + In = ParameterLocation.Path + }, + null, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(OpenApiParameter), + TargetValue = null, + SourceValue = new OpenApiParameter + { + Name = "pathParam", + In = ParameterLocation.Path + } + } + } + }; + + // Differences in target and source + yield return new object[] + { + "Differences in target and source", + new OpenApiParameter + { + Name = "pathParam", + Description = "Sample path parameter description", + In = ParameterLocation.Path, + Required = true, + AllowEmptyValue = true, + AllowReserved = true, + Style = ParameterStyle.Form, + Deprecated = false, + Explode = false, + Schema = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + new OpenApiParameter + { + Name = "pathParamUpdate", + Description = "Updated Sample path parameter description", + In = ParameterLocation.Query, + Required = false, + AllowEmptyValue = false, + AllowReserved = false, + Style = ParameterStyle.Label, + Deprecated = true, + Explode = true, + Schema = new OpenApiSchema + { + Type = "bool", + MaxLength = 15 + }, + Content = + { + ["text/plain"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/content/text~1plain", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = new KeyValuePair("text/plain", new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + }), + SourceValue = null + }, + new OpenApiDifference + { + Pointer = "#/content/application~1json", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("application/json", + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = "#/description", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string), + SourceValue = "Sample path parameter description", + TargetValue = "Updated Sample path parameter description" + }, + new OpenApiDifference + { + Pointer = "#/required", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?), + TargetValue = false, + SourceValue = true + }, + new OpenApiDifference + { + Pointer = "#/name", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string), + SourceValue = "pathParam", + TargetValue = "pathParamUpdate" + }, + new OpenApiDifference + { + Pointer = "#/deprecated", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?), + TargetValue = true, + SourceValue = false + }, + new OpenApiDifference + { + Pointer = "#/allowEmptyValue", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?), + TargetValue = false, + SourceValue = true + }, + new OpenApiDifference + { + Pointer = "#/explode", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?), + TargetValue = true, + SourceValue = false + }, + new OpenApiDifference + { + Pointer = "#/allowReserved", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?), + TargetValue = false, + SourceValue = true + }, + new OpenApiDifference + { + Pointer = "#/style", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(ParameterStyle), + SourceValue = ParameterStyle.Form, + TargetValue = ParameterStyle.Label + }, + new OpenApiDifference + { + Pointer = "#/in", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(ParameterLocation), + SourceValue = ParameterLocation.Path, + TargetValue = ParameterLocation.Query + }, + + new OpenApiDifference + { + Pointer = "#/schema/type", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string), + SourceValue = "string", + TargetValue = "bool" + } + } + }; + } + + [Theory] + [MemberData(nameof(GetTestCasesForOpenApiParameterComparerShouldSucceed))] + public void OpenApiParameterComparerShouldSucceed( + string testCaseName, + OpenApiParameter source, + OpenApiParameter target, + List expectedDifferences) + { + _output.WriteLine(testCaseName); + + var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), _sourceDocument, + _targetDocument); + var comparer = new OpenApiParameterComparer(); + comparer.Compare(source, target, comparisonContext); + + var differences = comparisonContext.OpenApiDifferences.ToList(); + differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); + + differences.ShouldBeEquivalentTo(expectedDifferences); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiParametersComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiParametersComparerTests.cs new file mode 100644 index 000000000..78aa6e512 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiParametersComparerTests.cs @@ -0,0 +1,432 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.OpenApi.Tests.Services +{ + [Collection("DefaultSettings")] + public class OpenApiParametersComparerTests + { + private readonly ITestOutputHelper _output; + + private readonly OpenApiDocument _sourceDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property7"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + private readonly OpenApiDocument _targetDocument = new OpenApiDocument + { + Components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["schemaObject1"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject2" + } + } + } + }, + ["schemaObject2"] = new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + }, + ["property5"] = new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }, + ["property6"] = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "schemaObject1" + } + } + } + } + }, + RequestBodies = new Dictionary + { + ["requestBody1"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + }, + ["requestBody2"] = new OpenApiRequestBody + { + Description = "description", + Required = true, + Content = + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = "schemaObject1", + Type = ReferenceType.Schema + } + } + } + } + } + } + } + }; + + public OpenApiParametersComparerTests(ITestOutputHelper output) + { + _output = output; + } + + public static IEnumerable GetTestCasesForOpenApiParametersComparerShouldSucceed() + { + // Source and Target are null + yield return new object[] + { + "Source and Target are null", + null, + null, + new List() + }; + + // Source and Target are empty + yield return new object[] + { + "Source and Target are null", + new List(), + new List(), + new List() + }; + + // Source is null + yield return new object[] + { + "Source is null", + null, + new List + { + new OpenApiParameter + { + Name = "pathParam1", + In = ParameterLocation.Path + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(IList), + SourceValue = null, + TargetValue = new List + { + new OpenApiParameter + { + Name = "pathParam1", + In = ParameterLocation.Path + } + } + } + } + }; + + // Target is null + yield return new object[] + { + "Target is null", + new List + { + new OpenApiParameter + { + Name = "pathParam1", + In = ParameterLocation.Path + } + }, + null, + new List + { + new OpenApiDifference + { + Pointer = "#/", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(IList), + TargetValue = null, + SourceValue = new List + { + new OpenApiParameter + { + Name = "pathParam1", + In = ParameterLocation.Path + } + } + } + } + }; + + // New, Removed and Updated Parameters + yield return new object[] + { + "New, Removed and Updated Parameters", + new List + { + new OpenApiParameter + { + Name = "pathParam1", + In = ParameterLocation.Path + }, + new OpenApiParameter + { + Name = "pathParam2", + In = ParameterLocation.Path + }, + new OpenApiParameter + { + Name = "pathParam3", + In = ParameterLocation.Path, + Description = "Sample path parameter description" + }, + new OpenApiParameter + { + Name = "queryParam1", + In = ParameterLocation.Query + }, + new OpenApiParameter + { + Name = "queryParam2", + In = ParameterLocation.Query + } + }, + new List + { + new OpenApiParameter + { + Name = "queryParam1", + In = ParameterLocation.Query + }, + new OpenApiParameter + { + Name = "pathParam1", + In = ParameterLocation.Path + }, + new OpenApiParameter + { + Name = "queryParam3", + In = ParameterLocation.Query + }, + new OpenApiParameter + { + Name = "pathParam3", + In = ParameterLocation.Path, + Description = "Updated Sample path parameter description" + } + }, + new List + { + new OpenApiDifference + { + Pointer = "#/4", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(OpenApiParameter), + TargetValue = null, + SourceValue = new OpenApiParameter + { + Name = "queryParam2", + In = ParameterLocation.Query + } + }, + new OpenApiDifference + { + Pointer = "#/1", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(OpenApiParameter), + TargetValue = null, + SourceValue = new OpenApiParameter + { + Name = "pathParam2", + In = ParameterLocation.Path + } + }, + new OpenApiDifference + { + Pointer = "#/2", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(OpenApiParameter), + SourceValue = null, + TargetValue = new OpenApiParameter + { + Name = "queryParam3", + In = ParameterLocation.Query + } + }, + new OpenApiDifference + { + Pointer = "#/3/description", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string), + SourceValue = "Sample path parameter description", + TargetValue = "Updated Sample path parameter description" + } + } + }; + } + + [Theory] + [MemberData(nameof(GetTestCasesForOpenApiParametersComparerShouldSucceed))] + public void OpenApiParametersComparerShouldSucceed( + string testCaseName, + IList source, + IList target, + List expectedDifferences) + { + _output.WriteLine(testCaseName); + + var comparisonContext = new ComparisonContext(new OpenApiComparerFactory(), _sourceDocument, + _targetDocument); + var comparer = new OpenApiParametersComparer(); + comparer.Compare(source, target, comparisonContext); + + var differences = comparisonContext.OpenApiDifferences.ToList(); + differences.Count().ShouldBeEquivalentTo(expectedDifferences.Count); + + differences.ShouldBeEquivalentTo(expectedDifferences); + } + } +} \ No newline at end of file From 536ea5cdfc4e04f4d694b2ce75144a054acf39c8 Mon Sep 17 00:00:00 2001 From: Shweta Patil Date: Mon, 1 Oct 2018 15:15:19 -0700 Subject: [PATCH 8/9] Fix test failure --- .../Services/OpenApiComparerTestCases.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs index 5242bd769..8d24170d6 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs @@ -735,7 +735,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { new OpenApiDifference { - Pointer = "#/paths/~1test/get/parameters/0/properties/property5", + Pointer = "#/paths/~1test/get/parameters/0/schema/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -747,7 +747,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new OpenApiDifference { - Pointer = "#/paths/~1test/get/parameters/0/properties/property7", + Pointer = "#/paths/~1test/get/parameters/0/schema/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema @@ -760,7 +760,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( new OpenApiDifference { Pointer = - "#/paths/~1test/get/parameters/0/properties/property6/properties/property6/properties/property5", + "#/paths/~1test/get/parameters/0/schema/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -773,7 +773,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( new OpenApiDifference { Pointer = - "#/paths/~1test/get/parameters/0/properties/property6/properties/property6/properties/property7", + "#/paths/~1test/get/parameters/0/schema/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema From af90775e74b45a46836c82d32bf67ea2a5c9b271 Mon Sep 17 00:00:00 2001 From: Shweta Patil Date: Mon, 1 Oct 2018 15:24:18 -0700 Subject: [PATCH 9/9] Fix pointer to include schema in path --- .../Services/OpenApiMediaTypeComparer.cs | 9 ++++++--- .../Services/OpenApiComparerTestCases.cs | 16 ++++++++-------- .../Services/OpenApiComponentsTests.cs | 8 ++++---- .../Services/OpenApiRequestBodyComparerTests.cs | 8 ++++---- .../Services/OpenApiResponsesComparerTests.cs | 8 ++++---- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs index 3eeac4356..9578e211d 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs @@ -42,9 +42,12 @@ public override void Compare( return; } - comparisonContext - .GetComparer() - .Compare(sourceMediaType.Schema, targetMediaType.Schema, comparisonContext); + WalkAndCompare( + comparisonContext, + OpenApiConstants.Schema, + () => comparisonContext + .GetComparer() + .Compare( sourceMediaType.Schema, targetMediaType.Schema, comparisonContext ) ); WalkAndCompare( comparisonContext, diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs index 8d24170d6..7728a7932 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTestCases.cs @@ -1127,7 +1127,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( { new OpenApiDifference { - Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/properties/property5", + Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/schema/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -1139,7 +1139,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new OpenApiDifference { - Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/properties/property7", + Pointer = "#/paths/~1test/get/requestBody/content/application~1xml/schema/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema @@ -1152,7 +1152,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( new OpenApiDifference { Pointer = - "#/paths/~1test/get/requestBody/content/application~1xml/properties/property6/properties/property6/properties/property5", + "#/paths/~1test/get/requestBody/content/application~1xml/schema/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -1165,7 +1165,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( new OpenApiDifference { Pointer = - "#/paths/~1test/get/requestBody/content/application~1xml/properties/property6/properties/property6/properties/property7", + "#/paths/~1test/get/requestBody/content/application~1xml/schema/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema @@ -1199,7 +1199,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( new OpenApiDifference { Pointer = - "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property5", + "#/paths/~1test/get/responses/200/content/application~1json/schema/items/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -1212,7 +1212,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( new OpenApiDifference { Pointer = - "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property7", + "#/paths/~1test/get/responses/200/content/application~1json/schema/items/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema @@ -1225,7 +1225,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( new OpenApiDifference { Pointer = - "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property6/properties/property6/properties/property5", + "#/paths/~1test/get/responses/200/content/application~1json/schema/items/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -1238,7 +1238,7 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( new OpenApiDifference { Pointer = - "#/paths/~1test/get/responses/200/content/application~1json/items/properties/property6/properties/property6/properties/property7", + "#/paths/~1test/get/responses/200/content/application~1json/schema/items/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs index 66defde0d..2b2fcdfe3 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs @@ -373,7 +373,7 @@ public static IEnumerable GetTestCasesForOpenApiComponentsComparerShou { new OpenApiDifference { - Pointer = "#/requestBodies/requestBody1/content/application~1json/properties/property5", + Pointer = "#/requestBodies/requestBody1/content/application~1json/schema/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -385,7 +385,7 @@ public static IEnumerable GetTestCasesForOpenApiComponentsComparerShou }, new OpenApiDifference { - Pointer = "#/requestBodies/requestBody1/content/application~1json/properties/property7", + Pointer = "#/requestBodies/requestBody1/content/application~1json/schema/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema @@ -398,7 +398,7 @@ public static IEnumerable GetTestCasesForOpenApiComponentsComparerShou new OpenApiDifference { Pointer = - "#/requestBodies/requestBody1/content/application~1json/properties/property6/properties/property6/properties/property5", + "#/requestBodies/requestBody1/content/application~1json/schema/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -411,7 +411,7 @@ public static IEnumerable GetTestCasesForOpenApiComponentsComparerShou new OpenApiDifference { Pointer = - "#/requestBodies/requestBody1/content/application~1json/properties/property6/properties/property6/properties/property7", + "#/requestBodies/requestBody1/content/application~1json/schema/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs index 2c1b9b596..211df3c1e 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs @@ -511,7 +511,7 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho { new OpenApiDifference { - Pointer = "#/content/application~1json/properties/property5", + Pointer = "#/content/application~1json/schema/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -523,7 +523,7 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho }, new OpenApiDifference { - Pointer = "#/content/application~1json/properties/property7", + Pointer = "#/content/application~1json/schema/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema @@ -536,7 +536,7 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho new OpenApiDifference { Pointer = - "#/content/application~1json/properties/property6/properties/property6/properties/property5", + "#/content/application~1json/schema/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -549,7 +549,7 @@ public static IEnumerable GetTestCasesForOpenApiRequestBodyComparerSho new OpenApiDifference { Pointer = - "#/content/application~1json/properties/property6/properties/property6/properties/property7", + "#/content/application~1json/schema/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs index cc8c4f9c1..8e61fb3f9 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs @@ -742,7 +742,7 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul { new OpenApiDifference { - Pointer = "#/200/content/application~1json/properties/property5", + Pointer = "#/200/content/application~1json/schema/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -754,7 +754,7 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul }, new OpenApiDifference { - Pointer = "#/200/content/application~1json/properties/property7", + Pointer = "#/200/content/application~1json/schema/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema @@ -767,7 +767,7 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul new OpenApiDifference { Pointer = - "#/200/content/application~1json/properties/property6/properties/property6/properties/property5", + "#/200/content/application~1json/schema/properties/property6/properties/property6/properties/property5", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = null, @@ -780,7 +780,7 @@ public static IEnumerable GetTestCasesForOpenApiResponsesComparerShoul new OpenApiDifference { Pointer = - "#/200/content/application~1json/properties/property6/properties/property6/properties/property7", + "#/200/content/application~1json/schema/properties/property6/properties/property6/properties/property7", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, OpenApiComparedElementType = typeof(KeyValuePair), SourceValue = new KeyValuePair("property7", new OpenApiSchema