From a6fe2685595de229395fbb524a53700bd5a291f3 Mon Sep 17 00:00:00 2001 From: Joel Verhagen Date: Fri, 3 Jan 2020 16:47:39 -0800 Subject: [PATCH] Add unit tests for RegistrationComparer core logic (#730) Progress on https://github.com/NuGet/NuGetGallery/issues/7741 --- NuGet.Services.Metadata.sln | 15 ++ .../Normalizers.cs | 2 +- test.ps1 | 3 +- .../App.config | 6 + .../JsonComparerFacts.cs | 206 ++++++++++++++++++ ...Get.Jobs.RegistrationComparer.Tests.csproj | 79 +++++++ .../Properties/AssemblyInfo.cs | 9 + 7 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 tests/NuGet.Jobs.RegistrationComparer.Tests/App.config create mode 100644 tests/NuGet.Jobs.RegistrationComparer.Tests/JsonComparerFacts.cs create mode 100644 tests/NuGet.Jobs.RegistrationComparer.Tests/NuGet.Jobs.RegistrationComparer.Tests.csproj create mode 100644 tests/NuGet.Jobs.RegistrationComparer.Tests/Properties/AssemblyInfo.cs diff --git a/NuGet.Services.Metadata.sln b/NuGet.Services.Metadata.sln index d69d46f8f..0f45d1809 100644 --- a/NuGet.Services.Metadata.sln +++ b/NuGet.Services.Metadata.sln @@ -73,6 +73,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet.Services.V3.Tests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet.Jobs.RegistrationComparer", "src\NuGet.Jobs.RegistrationComparer\NuGet.Jobs.RegistrationComparer.csproj", "{4CE6C864-DB4D-4262-A2DD-80BB932F6E8C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet.Jobs.RegistrationComparer.Tests", "tests\NuGet.Jobs.RegistrationComparer.Tests\NuGet.Jobs.RegistrationComparer.Tests.csproj", "{A0E0698A-1161-4DEA-81A9-06D30FB16538}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -391,6 +393,18 @@ Global {4CE6C864-DB4D-4262-A2DD-80BB932F6E8C}.Release|Mixed Platforms.Build.0 = Release|Any CPU {4CE6C864-DB4D-4262-A2DD-80BB932F6E8C}.Release|x64.ActiveCfg = Release|Any CPU {4CE6C864-DB4D-4262-A2DD-80BB932F6E8C}.Release|x64.Build.0 = Release|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Debug|x64.ActiveCfg = Debug|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Debug|x64.Build.0 = Debug|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Release|Any CPU.Build.0 = Release|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Release|x64.ActiveCfg = Release|Any CPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -422,6 +436,7 @@ Global {296703A3-67BA-4876-8C1D-ACE13DF901EF} = {F1C83FD9-A498-483E-ADFA-B55D82A14965} {CCB4D5EF-AC84-449D-AC6E-0A0AD295483A} = {F1C83FD9-A498-483E-ADFA-B55D82A14965} {4CE6C864-DB4D-4262-A2DD-80BB932F6E8C} = {C86C6DEE-84E1-4E4E-8868-6755D7A8E0E4} + {A0E0698A-1161-4DEA-81A9-06D30FB16538} = {F1C83FD9-A498-483E-ADFA-B55D82A14965} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D3AB83E9-02B4-4FFA-A2D0-637F0B97E626} diff --git a/src/NuGet.Jobs.RegistrationComparer/Normalizers.cs b/src/NuGet.Jobs.RegistrationComparer/Normalizers.cs index ae237abfd..66fc72824 100644 --- a/src/NuGet.Jobs.RegistrationComparer/Normalizers.cs +++ b/src/NuGet.Jobs.RegistrationComparer/Normalizers.cs @@ -236,7 +236,7 @@ private static bool IsPropertyName(string path, string name) return path == name || path.EndsWith("." + name); } - private Normalizers( + public Normalizers( IReadOnlyList scalarNormalizers, IReadOnlyList unsortedObjects, IReadOnlyList unsortedArrays) diff --git a/test.ps1 b/test.ps1 index e0bb1a752..8642c7eb4 100644 --- a/test.ps1 +++ b/test.ps1 @@ -33,7 +33,8 @@ Function Run-Tests { "tests\NuGet.Protocol.Catalog.Tests\bin\$Configuration\NuGet.Protocol.Catalog.Tests.dll", ` "tests\NuGet.Services.AzureSearch.Tests\bin\$Configuration\NuGet.Services.AzureSearch.Tests.dll", ` "tests\NuGet.Services.SearchService.Tests\bin\$Configuration\NuGet.Services.SearchService.Tests.dll", ` - "tests\NuGet.Jobs.Catalog2Registration.Tests\bin\$Configuration\NuGet.Jobs.Catalog2Registration.Tests.dll" + "tests\NuGet.Jobs.Catalog2Registration.Tests\bin\$Configuration\NuGet.Jobs.Catalog2Registration.Tests.dll", ` + "tests\NuGet.Jobs.RegistrationComparer.Tests\bin\$Configuration\NuGet.Jobs.RegistrationComparer.Tests.dll" $TestCount = 0 diff --git a/tests/NuGet.Jobs.RegistrationComparer.Tests/App.config b/tests/NuGet.Jobs.RegistrationComparer.Tests/App.config new file mode 100644 index 000000000..2a2d44990 --- /dev/null +++ b/tests/NuGet.Jobs.RegistrationComparer.Tests/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/NuGet.Jobs.RegistrationComparer.Tests/JsonComparerFacts.cs b/tests/NuGet.Jobs.RegistrationComparer.Tests/JsonComparerFacts.cs new file mode 100644 index 000000000..019b28333 --- /dev/null +++ b/tests/NuGet.Jobs.RegistrationComparer.Tests/JsonComparerFacts.cs @@ -0,0 +1,206 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; +using ValueNormalizer = System.Collections.Generic.KeyValuePair; +using ArrayNormalizer = System.Collections.Generic.KeyValuePair>; + +namespace NuGet.Jobs.RegistrationComparer +{ + public class JsonComparerFacts + { + [Fact] + public void AcceptsSameObject() + { + var a = Json(new { array = new[] { 0, 1, 3 } }); + var b = Json(new { array = new[] { 0, 1, 3 } }); + + Target.Compare(a, b, Context); + + Assert.NotSame(a, b); + } + + [Fact] + public void DetectsMissingItemInArray() + { + var a = Json(new { array = new[] { 0, 1, 3 } }); + var b = Json(new { array = new[] { 0, 1, 2, 3 } }); + + var ex = Assert.Throws(() => Target.Compare(a, b, Context)); + + Assert.Contains("The JSON array item count is different.", ex.Message); + } + + [Fact] + public void DetectsDifferentItemsInArray() + { + var a = Json(new { array = new[] { 0, 1, 3 } }); + var b = Json(new { array = new[] { 0, 1, 2 } }); + + var ex = Assert.Throws(() => Target.Compare(a, b, Context)); + + Assert.Contains("The value of the JSON scalar is different.", ex.Message); + } + + [Fact] + public void DetectsOutOfOrderItemsInArray() + { + var a = Json(new { array = new[] { 0, 2, 1 } }); + var b = Json(new { array = new[] { 0, 1, 2 } }); + + var ex = Assert.Throws(() => Target.Compare(a, b, Context)); + + Assert.Contains("The value of the JSON scalar is different.", ex.Message); + } + + [Theory] + [InlineData("2", 2)] + [InlineData(true, 1)] + [InlineData("false", false)] + [InlineData("null", null)] + [InlineData(0, null)] + public void DetectsDifferentTypesInArray(object valueA, object valueB) + { + var a = Json(new { array = new object[] { 0, 1, valueA } }); + var b = Json(new { array = new object[] { 0, 1, valueB } }); + + var ex = Assert.Throws(() => Target.Compare(a, b, Context)); + + Assert.Contains("The type of the JSON value is different.", ex.Message); + } + + [Fact] + public void DetectsDifferentProperties() + { + var a = Json(new { arrayA = new[] { 0, 1, 2 } }); + var b = Json(new { arrayB = new[] { 0, 1, 2 } }); + + var ex = Assert.Throws(() => Target.Compare(a, b, Context)); + + Assert.Contains("The JSON object property names are disjoint.", ex.Message); + } + + [Fact] + public void DetectsOutOfOrderProperties() + { + var a = Json(new { inner = new { a = "a", b = "b" } }); + var b = Json(new { inner = new { b = "b", a = "a" } }); + + var ex = Assert.Throws(() => Target.Compare(a, b, Context)); + + Assert.Contains("The JSON object property names are in a different order.", ex.Message); + } + + [Fact] + public void DetectsDifferentCaseOfPropertyNames() + { + var a = Json(new { array = new[] { 0, 1, 2 } }); + var b = Json(new { Array = new[] { 0, 1, 2 } }); + + var ex = Assert.Throws(() => Target.Compare(a, b, Context)); + + Assert.Contains("The JSON object property names are disjoint.", ex.Message); + } + + [Fact] + public void DetectsExtraProperty() + { + var a = Json(new { array = new[] { 0, 1, 2 } }); + var b = Json(new { array = new[] { 0, 1, 2 }, somethingElse = 2 }); + + var ex = Assert.Throws(() => Target.Compare(a, b, Context)); + + Assert.Contains("The JSON object property names are disjoint.", ex.Message); + } + + [Fact] + public void AllowsValueToBeNormalized() + { + var normalizers = new Normalizers( + scalarNormalizers: new List + { + new ValueNormalizer( + (path) => path == "random", + (token, isLeft, context) => "999"), + }, + unsortedObjects: new List(), + unsortedArrays: new List()); + var a = Json(new { array = new[] { 0, 1, 2 }, random = 23 }); + var b = Json(new { array = new[] { 0, 1, 2 }, random = 42 }); + + Target.Compare(a, b, GetContext(normalizers)); + } + + [Fact] + public void AllowsObjectPropertyOrderToBeIgnored() + { + var normalizers = new Normalizers( + scalarNormalizers: new List(), + unsortedObjects: new List + { + (path) => path == "inner", + }, + unsortedArrays: new List()); + var a = Json(new { inner = new { a = "a", b = "b" } }); + var b = Json(new { inner = new { b = "b", a = "a" } }); + + Target.Compare(a, b, GetContext(normalizers)); + } + + [Fact] + public void AllowsArrayItemOrderToBeIgnored() + { + var normalizers = new Normalizers( + scalarNormalizers: new List(), + unsortedObjects: new List(), + unsortedArrays: new List + { + new ArrayNormalizer( + array => array.Path == "array", + (x, y) => Comparer.Default.Compare(x, y)), + }); + var a = Json(new { array = new[] { 0, 2, 1 } }); + var b = Json(new { array = new[] { 0, 1, 2 } }); + + Target.Compare(a, b, GetContext(normalizers)); + } + + public JsonComparerFacts() + { + Context = GetContext(); + Target = new JsonComparer(); + } + + private ComparisonContext GetContext(Normalizers normalizers) + { + return new ComparisonContext( + "NuGet.Versioning", + "https://example/api/a", + "https://example/api/b", + "https://example/api/a/index.json", + "https://example/api/b/index.json", + normalizers); + } + + private ComparisonContext GetContext() + { + return GetContext( + new Normalizers( + new List(), + new List(), + new List())); + } + + public ComparisonContext Context { get; } + public JsonComparer Target { get; } + + private JToken Json(T obj) + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + } + } +} diff --git a/tests/NuGet.Jobs.RegistrationComparer.Tests/NuGet.Jobs.RegistrationComparer.Tests.csproj b/tests/NuGet.Jobs.RegistrationComparer.Tests/NuGet.Jobs.RegistrationComparer.Tests.csproj new file mode 100644 index 000000000..5ae96dfba --- /dev/null +++ b/tests/NuGet.Jobs.RegistrationComparer.Tests/NuGet.Jobs.RegistrationComparer.Tests.csproj @@ -0,0 +1,79 @@ + + + + + Debug + AnyCPU + {A0E0698A-1161-4DEA-81A9-06D30FB16538} + Library + Properties + NuGet.Jobs.RegistrationComparer + NuGet.Jobs.RegistrationComparer.Tests + v4.7.2 + 512 + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + 4.10.1 + + + 2.4.1 + + + 2.4.1 + runtime; build; native; contentfiles; analyzers + all + + + + + + + + + + + + {4ce6c864-db4d-4262-a2dd-80bb932f6e8c} + NuGet.Jobs.RegistrationComparer + + + + + ..\..\build + $(BUILD_SOURCESDIRECTORY)\build + $(NuGetBuildPath) + + + \ No newline at end of file diff --git a/tests/NuGet.Jobs.RegistrationComparer.Tests/Properties/AssemblyInfo.cs b/tests/NuGet.Jobs.RegistrationComparer.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..32478c614 --- /dev/null +++ b/tests/NuGet.Jobs.RegistrationComparer.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("NuGet.Jobs.RegistrationComparer.Tests")] +[assembly: ComVisible(false)] +[assembly: Guid("a83b83d0-4f95-4e1e-bb46-8c4ce547bd67")]