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..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()); + 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 2351f9277..d08b5e644 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 @@ -63,7 +64,73 @@ 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 + }); + } + } + + /// + /// 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 + }); + } + } + + /// + /// 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(TEnum), + SourceValue = source, + TargetValue = target, + Pointer = comparisonContext.PathString + }); + + return; + } + + if (!source.Equals(target)) + { + comparisonContext.AddOpenApiDifference(new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(TEnum), SourceValue = source, TargetValue = target, Pointer = comparisonContext.PathString @@ -82,7 +149,7 @@ internal void WalkAndAddOpenApiDifference( string segment, OpenApiDifference openApiDifference) { - comparisonContext.Enter(segment.Replace("/", "~1")); + comparisonContext.Enter(segment.Replace("~", "~0").Replace("/", "~1")); openApiDifference.Pointer = comparisonContext.PathString; comparisonContext.AddOpenApiDifference(openApiDifference); comparisonContext.Exit(); @@ -99,7 +166,7 @@ protected virtual void WalkAndCompare( string segment, Action compare) { - comparisonContext.Enter(segment.Replace("/", "~1")); + comparisonContext.Enter(segment.Replace("~", "~0").Replace("/", "~1")); compare(); comparisonContext.Exit(); } diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs b/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs index f8ce27c1b..e10f0aa5f 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiComparerFactory.cs @@ -19,7 +19,28 @@ 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(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(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/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..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,21 +15,35 @@ 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, OpenApiDocument targetDocument, ComparisonContext comparisonContext) { - comparisonContext.GetComparer().Compare( - sourceDocument.Paths, - targetDocument.Paths, - comparisonContext); + WalkAndCompare( + comparisonContext, + OpenApiConstants.Paths, + () => comparisonContext + .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..923ab1420 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiEncodingComparer.cs @@ -0,0 +1,67 @@ +// 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..b57ff4071 --- /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, + OpenApiConstants.DollarRef, + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceHeader.Reference, + TargetValue = targetHeader.Reference, + OpenApiComparedElementType = typeof(OpenApiReference) + }); + + return; + } + + if (sourceHeader.Reference != null) + { + sourceHeader = (OpenApiHeader) comparisonContext.SourceDocument.ResolveReference( + sourceHeader.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 new file mode 100644 index 000000000..9578e211d --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiMediaTypeComparer.cs @@ -0,0 +1,64 @@ +// 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 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; + } + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Schema, + () => comparisonContext + .GetComparer() + .Compare( sourceMediaType.Schema, targetMediaType.Schema, comparisonContext ) ); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Encoding, + () => comparisonContext + .GetComparer>() + .Compare(sourceMediaType.Encoding, sourceMediaType.Encoding, 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/OpenApiOperationComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs index 362bc65df..98879e013 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiOperationComparer.cs @@ -52,13 +52,26 @@ 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)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Servers, + () => comparisonContext + .GetComparer>() + .Compare(sourceOperation?.Servers, targetOperation?.Servers, comparisonContext)); - // Compare Responses - // Compare Request Body // Compare CallBack // Compare Security Requirements // Compare Extensions - // Compare Servers // Compare External Docs // Compare Tags } 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..53d3e6619 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,66 @@ 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; + } + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Content, + () => comparisonContext + .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 545ef5dbf..f6ad885ef 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiParametersComparer.cs @@ -30,54 +30,68 @@ public override void Compare( return; } - if (!sourceParameters.Any() && !targetParameters.Any()) + if (sourceParameters == null || targetParameters == null) { + comparisonContext.AddOpenApiDifference( + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + SourceValue = sourceParameters, + TargetValue = targetParameters, + OpenApiComparedElementType = typeof(IList), + Pointer = comparisonContext.PathString + }); + return; } - var newParametersInTarget = targetParameters?.Where( - targetParam => !sourceParameters.Any( - sourceParam => sourceParam.Name == targetParam.Name && sourceParam.In == targetParam.In)).ToList(); + var removedParameters = sourceParameters?.Where( + sourceParam => !targetParameters.Any( + targetParam => sourceParam.Name == targetParam.Name && sourceParam.In == targetParam.In)).ToList(); - for (var i = 0; i < newParametersInTarget?.Count; i++) + for (var i = removedParameters.Count - 1; i >= 0; i--) { WalkAndAddOpenApiDifference( comparisonContext, - i.ToString(), + sourceParameters.IndexOf(removedParameters[i]).ToString(), new OpenApiDifference { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - TargetValue = targetParameters[i], + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = removedParameters[i], OpenApiComparedElementType = typeof(OpenApiParameter) }); } - var removedParameters = sourceParameters?.Where( - sourceParam => !targetParameters.Any( - targetParam => sourceParam.Name == targetParam.Name && sourceParam.In == targetParam.In)).ToList(); + var newParametersInTarget = targetParameters?.Where( + targetParam => !sourceParameters.Any( + sourceParam => sourceParam.Name == targetParam.Name && sourceParam.In == targetParam.In)).ToList(); - for (var i = 0; i < removedParameters.Count; i++) + foreach (var newParameterInTarget in newParametersInTarget) { WalkAndAddOpenApiDifference( comparisonContext, - i.ToString(), + targetParameters.IndexOf(newParameterInTarget).ToString(), new OpenApiDifference { - OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - SourceValue = removedParameters[i], + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = newParameterInTarget, OpenApiComparedElementType = typeof(OpenApiParameter) }); } - for (var i = 0; i < sourceParameters.Count; i++) + foreach (var sourceParameter in sourceParameters) { - var sourceParameter = sourceParameters[i]; var targetParameter = targetParameters .FirstOrDefault(param => param.Name == sourceParameter.Name && param.In == sourceParameter.In); + if (targetParameter == null) + { + continue; + } + WalkAndCompare( comparisonContext, - i.ToString(), + targetParameters.IndexOf(targetParameter).ToString(), () => comparisonContext .GetComparer() .Compare(sourceParameter, targetParameter, comparisonContext)); diff --git a/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs index 7ae1ef758..84c590eee 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiPathItemComparer.cs @@ -40,8 +40,20 @@ public override void Compare( comparisonContext.GetComparer>() .Compare(sourcePathItem?.Operations, targetPathItem?.Operations, comparisonContext); - // To Do Compare Servers - // To Do Compare Parameters + WalkAndCompare( + comparisonContext, + OpenApiConstants.Parameters, + () => comparisonContext + .GetComparer>() + .Compare(sourcePathItem?.Parameters, targetPathItem?.Parameters, comparisonContext)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Servers, + () => comparisonContext + .GetComparer>() + .Compare(sourcePathItem?.Servers, targetPathItem?.Servers, comparisonContext)); + // 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..880c654cf --- /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, + OpenApiConstants.DollarRef, + 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..805ad2743 --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiResponseComparer.cs @@ -0,0 +1,96 @@ +// 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, + OpenApiConstants.DollarRef, + 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)); + + WalkAndCompare( + comparisonContext, + OpenApiConstants.Headers, + () => comparisonContext + .GetComparer>() + .Compare(sourceResponse.Headers, targetResponse.Headers, comparisonContext)); + + // 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..e320854fd --- /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, + OpenApiConstants.Items, + () => comparisonContext + .GetComparer() + .Compare(sourceSchema.Items, targetSchema.Items, comparisonContext)); + } + + if (sourceSchema.Reference != null + && targetSchema.Reference != null + && sourceSchema.Reference.Id != targetSchema.Reference.Id) + { + WalkAndAddOpenApiDifference( + comparisonContext, + OpenApiConstants.DollarRef, + 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, + SourceValue = 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/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..00be2544e --- /dev/null +++ b/src/Microsoft.OpenApi/Services/OpenApiServersComparer.cs @@ -0,0 +1,99 @@ +// 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 removedServers = sourceServers.Where( + sourceServer => targetServers.All(targetServer => sourceServer.Url != targetServer.Url)).ToList(); + + for (var i = removedServers.Count - 1; i >= 0; i--) + { + WalkAndAddOpenApiDifference( + comparisonContext, + removedServers.IndexOf(removedServers[i]).ToString(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + SourceValue = removedServers[i], + OpenApiComparedElementType = typeof(OpenApiServer) + }); + } + + var newServersInTarget = targetServers.Where( + targetServer => sourceServers.All(sourceServer => sourceServer.Url != targetServer.Url)).ToList(); + + foreach (var newServerInTarget in newServersInTarget) + { + WalkAndAddOpenApiDifference( + comparisonContext, + targetServers.IndexOf(newServerInTarget).ToString(), + new OpenApiDifference + { + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + TargetValue = newServerInTarget, + OpenApiComparedElementType = typeof(OpenApiServer) + }); + } + + foreach (var sourceServer in sourceServers) + { + var targetServer = targetServers + .FirstOrDefault(server => server.Url == sourceServer.Url); + + if (targetServer == null) + { + continue; + } + + WalkAndCompare( + comparisonContext, + targetServers.IndexOf(targetServer).ToString(), + () => comparisonContext + .GetComparer() + .Compare(sourceServer, targetServer, comparisonContext)); + } + } + } +} \ 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..7728a7932 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,17 +57,41 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { 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() + 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() + } + } + } } } }; @@ -120,17 +146,25 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/patch", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = + new KeyValuePair(OperationType.Patch, + new OpenApiOperation()) }, - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/post", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = null, + SourceValue = + new KeyValuePair(OperationType.Post, + new OpenApiOperation()) } } }; @@ -161,11 +195,28 @@ 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), + SourceValue = new OpenApiPaths + { + { + "/test", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } + } + }, + TargetValue = null } } }; @@ -196,11 +247,28 @@ 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), + SourceValue = null, + TargetValue = new OpenApiPaths + { + { + "/newPath", new OpenApiPathItem + { + Summary = "test", + Description = "test", + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation() + } + } + } + } + } } } }; @@ -247,17 +315,24 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/get", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = null, + SourceValue = + new KeyValuePair(OperationType.Get, new OpenApiOperation()) }, - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/post", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + TargetValue = null, + SourceValue = + new KeyValuePair(OperationType.Post, + new OpenApiOperation()) } } }; @@ -304,17 +379,24 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/get", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = + new KeyValuePair(OperationType.Get, new OpenApiOperation()) }, - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/patch", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, - OpenApiComparedElementType = typeof(KeyValuePair) + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = + new KeyValuePair(OperationType.Patch, + new OpenApiOperation()) } } }; @@ -420,17 +502,827 @@ public static IEnumerable GetTestCasesForOpenApiComparerShouldSucceed( }, new List { - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/summary", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + SourceValue = "test", + TargetValue = "updated" }, - new OpenApiDifference() + new OpenApiDifference { Pointer = "#/paths/~1test/description", OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, - OpenApiComparedElementType = typeof(string) + OpenApiComparedElementType = typeof(string), + SourceValue = "test", + TargetValue = "updated" + } + } + }; + + // 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/schema/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = "#/paths/~1test/get/parameters/0/schema/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = + "#/paths/~1test/get/parameters/0/schema/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 = + "#/paths/~1test/get/parameters/0/schema/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/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 + } + } + }; + + // 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/schema/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + 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/schema/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + 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/schema/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 = + "#/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 + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = "#/paths/~1test/get/responses/400", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + 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/schema/items/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + 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/schema/items/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + 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/schema/items/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 = + "#/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 + { + 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 947f659b7..306a40abe 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComparerTests.cs @@ -36,14 +36,7 @@ public void OpenApiComparerShouldSucceed( 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 ); - } + 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 new file mode 100644 index 000000000..2b2fcdfe3 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiComponentsTests.cs @@ -0,0 +1,703 @@ +// 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/schema/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = "#/requestBodies/requestBody1/content/application~1json/schema/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = + "#/requestBodies/requestBody1/content/application~1json/schema/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 = + "#/requestBodies/requestBody1/content/application~1json/schema/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 = "#/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 = "#/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 = + "#/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 = + "#/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 = "#/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 = "#/schemas/schemaObject2/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 = + "#/schemas/schemaObject2/properties/property6/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 = + "#/schemas/schemaObject2/properties/property6/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 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), + 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), + SourceValue = null, + TargetValue = new KeyValuePair("schemaObject2", new OpenApiSchema + { + Properties = new Dictionary + { + ["property2"] = new OpenApiSchema + { + Type = "integer" + } + } + }) + } + } + }; + } + + [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); + + 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 new file mode 100644 index 000000000..54057859d --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiEncodingComparerTests.cs @@ -0,0 +1,359 @@ +// 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), + TargetValue = "image/jpeg", + SourceValue = "image/png, image/jpeg" + }, + new OpenApiDifference + { + Pointer = "#/style", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(ParameterStyle), + TargetValue = ParameterStyle.Form, + SourceValue = ParameterStyle.Simple + }, + new OpenApiDifference + { + Pointer = "#/explode", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?), + TargetValue = false, + SourceValue = true + }, + new OpenApiDifference + { + Pointer = "#/allowReserved", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?), + TargetValue = false, + SourceValue = true + } + } + }; + + // 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), + SourceValue = null, + TargetValue = new OpenApiEncoding + { + ContentType = "image/jpeg", + Style = ParameterStyle.Form, + Explode = false, + AllowReserved = false + } + } + } + }; + + // 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), + TargetValue = null, + SourceValue = new OpenApiEncoding + { + ContentType = "image/jpeg", + Style = ParameterStyle.Form, + Explode = false, + AllowReserved = false + } + } + } + }; + } + + [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); + + differences.ShouldBeEquivalentTo(expectedDifferences); + } + } +} \ No newline at end of file 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 diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs new file mode 100644 index 000000000..211df3c1e --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiRequestBodyComparerTests.cs @@ -0,0 +1,588 @@ +// 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), + TargetValue = "udpated description", + SourceValue = "description" + }, + new OpenApiDifference + { + Pointer = "#/required", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(bool?), + TargetValue = false, + SourceValue = true + } + } + }; + + // 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), + 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), + TargetValue = null, + SourceValue = new KeyValuePair("application/json", + new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + }) + } + } + }; + + // 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), + SourceValue = null, + TargetValue = new OpenApiRequestBody + { + Description = "udpated description", + Required = false, + Content = + { + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + } + } + } + }; + + // 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), + SourceValue = new OpenApiRequestBody + { + Description = "udpated description", + Required = false, + Content = + { + ["application/xml"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string" + } + } + } + }, + TargetValue = null + } + } + }; + + // 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), + TargetValue = new OpenApiReference + { + Id = "NewId", + Type = ReferenceType.RequestBody + }, + SourceValue = new OpenApiReference + { + Id = "Id", + Type = ReferenceType.RequestBody + } + } + } + }; + + // 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/schema/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = "#/content/application~1json/schema/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = + "#/content/application~1json/schema/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 = + "#/content/application~1json/schema/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + } + } + }; + } + + [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); + + 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 new file mode 100644 index 000000000..8e61fb3f9 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiResponsesComparerTests.cs @@ -0,0 +1,819 @@ +// 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), + SourceValue = "A complex object array response", + TargetValue = "An updated complex object array response" + } + } + }; + + // 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), + 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), + 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" + } + } + } + } + } + }) + } + } + }; + + // 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), + 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), + 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" + } + } + } + }) + } + } + }; + + // 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), + 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" + } + } + } + } + } + } + } + } + } + } + }; + + // 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), + 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" + } + } + } + } + } + } + } + } + } + } + }; + + // 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), + SourceValue = new OpenApiReference + { + Id = "responseObject1", + Type = ReferenceType.Response + }, + TargetValue = new OpenApiReference + { + Id = "responseObject2", + Type = ReferenceType.Response + } + } + } + }; + + // 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/schema/properties/property5", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Add, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = null, + TargetValue = new KeyValuePair("property5", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }) + }, + new OpenApiDifference + { + Pointer = "#/200/content/application~1json/schema/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + }, + new OpenApiDifference + { + Pointer = + "#/200/content/application~1json/schema/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 = + "#/200/content/application~1json/schema/properties/property6/properties/property6/properties/property7", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Remove, + OpenApiComparedElementType = typeof(KeyValuePair), + SourceValue = new KeyValuePair("property7", new OpenApiSchema + { + Type = "string", + MaxLength = 15 + }), + TargetValue = null + } + } + }; + } + + [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); + + 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 new file mode 100644 index 000000000..0f689dca7 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiServerVariableComparerTests.cs @@ -0,0 +1,287 @@ +// 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), + SourceValue = "test description", + TargetValue = "test description updated" + }, + new OpenApiDifference + { + Pointer = "#/default", + OpenApiDifferenceOperation = OpenApiDifferenceOperation.Update, + OpenApiComparedElementType = typeof(string), + SourceValue = "8443", + TargetValue = "1003" + } + } + }; + } + + [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); + + 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 new file mode 100644 index 000000000..2719d2661 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiServersComparerTests.cs @@ -0,0 +1,517 @@ +// 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), + SourceValue = "description1", + TargetValue = "description2" + } + } + }; + + // 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), + 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), + 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), + 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" + } + } + } + } + } + }; + } + + [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); + + differences.ShouldBeEquivalentTo(expectedDifferences); + } + } +} \ No newline at end of file