From 51c9e5ef4d66c87f99fe8f81d096c33578099388 Mon Sep 17 00:00:00 2001 From: Billy Richardson Date: Fri, 13 Sep 2024 23:48:50 -0700 Subject: [PATCH 1/3] Add combination tests --- src/UnitTests/TaskTestBase.cs | 118 +++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/src/UnitTests/TaskTestBase.cs b/src/UnitTests/TaskTestBase.cs index 1b4a9ab..607d6e5 100644 --- a/src/UnitTests/TaskTestBase.cs +++ b/src/UnitTests/TaskTestBase.cs @@ -1,4 +1,4 @@ -using AggregateConfigBuildTask.Contracts; +using AggregateConfigBuildTask.Contracts; using Microsoft.Build.Framework; using Moq; using Newtonsoft.Json; @@ -477,6 +477,122 @@ public void StressTest_ShouldAddSourcePropertyManyFiles() } } + [TestMethod] + [Description("Test that files are correctly translated between ARM, JSON, and YAML.")] + public void ShouldTranslateBetweenFormatsAndValidateNoDataLoss() + { + // Test matrix for all combinations + var testCases = new[] + { + new { InputType = "arm", Step1OutputType = "json", Step2OutputType = "yml" }, + new { InputType = "arm", Step1OutputType = "yml", Step2OutputType = "json" }, + new { InputType = "json", Step1OutputType = "arm", Step2OutputType = "yml" }, + new { InputType = "json", Step1OutputType = "yml", Step2OutputType = "arm" }, + new { InputType = "yml", Step1OutputType = "arm", Step2OutputType = "json" }, + new { InputType = "yml", Step1OutputType = "json", Step2OutputType = "arm" } + }; + + Assert.IsTrue(testCases.Length > 0); + + foreach (var testCase in testCases) + { + // Arrange: Prepare paths and sample data based on the input type. + var paths = PrepareFilePaths(testCase.InputType, testCase.Step1OutputType, testCase.Step2OutputType); + + virtualFileSystem.CreateDirectory(paths.inputDir); + virtualFileSystem.CreateDirectory(paths.step1Dir); + virtualFileSystem.CreateDirectory(paths.step2Dir); + virtualFileSystem.CreateDirectory(paths.finalDir); + + virtualFileSystem.WriteAllText(paths.inputFilePath, GetSampleDataForType(testCase.InputType)); + + // Step 1: Convert Input -> Step 1 Output + ExecuteTranslationTask(testCase.InputType, testCase.Step1OutputType, paths.inputDir, paths.step1OutputPath); + + // Step 2: Convert Step 1 Output -> Step 2 Output + ExecuteTranslationTask(testCase.Step1OutputType, testCase.Step2OutputType, paths.step1Dir, paths.step2OutputPath); + + // Final Step: Convert Step 2 Output -> Original Input Type + ExecuteTranslationTask(testCase.Step2OutputType, testCase.InputType, paths.step2Dir, paths.finalOutputPath); + + // Assert: Compare final output with original input to check no data loss + AssertNoDataLoss(paths.inputFilePath, paths.finalOutputPath, testCase.InputType); + + // Clear the virtual file system for the next run + ((VirtualFileSystem)virtualFileSystem).FormatSystem(); + } + } + + private (string inputDir, string step1Dir, string step2Dir, string finalDir, string inputFilePath, string step1OutputPath, string step2OutputPath, string finalOutputPath) + PrepareFilePaths(string inputType, string step1OutputType, string step2OutputType) + { + string inputDir = $"{testPath}\\input"; + string step1Dir = $"{testPath}\\step1"; + string step2Dir = $"{testPath}\\step2"; + string finalDir = $"{testPath}\\final"; + + string inputFilePath = $"{inputDir}\\input.{(inputType == "arm" ? "json" : inputType)}"; + string step1OutputPath = $"{step1Dir}\\step1_output.{(step1OutputType == "arm" ? "json" : step1OutputType)}"; + string step2OutputPath = $"{step2Dir}\\step2_output.{(step2OutputType == "arm" ? "json" : step2OutputType)}"; + string finalOutputPath = $"{finalDir}\\final_output.{(inputType == "arm" ? "json" : inputType)}"; + + return (inputDir, step1Dir, step2Dir, finalDir, inputFilePath, step1OutputPath, step2OutputPath, finalOutputPath); + } + + private void ExecuteTranslationTask(string inputType, string outputType, string inputFilePath, string outputFilePath) + { + var task = new AggregateConfig(virtualFileSystem, mockLogger.Object) + { + InputDirectory = inputFilePath, + InputType = inputType, + OutputFile = outputFilePath, + OutputType = outputType, + BuildEngine = Mock.Of() + }; + bool result = task.Execute(); + Assert.IsTrue(result, $"Failed translation: {inputType} -> {outputType}"); + } + + private void AssertNoDataLoss(string originalFilePath, string finalFilePath, string inputType) + { + string originalInput = virtualFileSystem.ReadAllText(originalFilePath); + string finalOutput = virtualFileSystem.ReadAllText(finalFilePath); + Assert.AreEqual(originalInput, finalOutput, $"Data mismatch after full conversion cycle for {inputType}"); + } + + private static string GetSampleDataForType(string type) + { + return type switch + { + "json" => @"{ + ""options"": [ + { + ""name"": ""Option 1"", + ""description"": ""First option"" + } + ] +}", + "arm" => @"{ + ""$schema"": ""https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#"", + ""contentVersion"": ""1.0.0.0"", + ""parameters"": { + ""options"": { + ""type"": ""object"", + ""value"": { + ""name"": ""Option 1"", + ""description"": ""First option"" + } + } + } +}", + "yml" => @"options: +- name: Option 1 + description: First option +", + _ => throw new InvalidOperationException("Unknown type") + }; + } + /// /// Check if an option exists with a given name and source /// From 606e17019e5a23ecd5dddb91f3bfa7b5c423ce66 Mon Sep 17 00:00:00 2001 From: Billy Richardson Date: Fri, 13 Sep 2024 23:52:13 -0700 Subject: [PATCH 2/3] Add boolean and number test --- src/UnitTests/TaskTestBase.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/UnitTests/TaskTestBase.cs b/src/UnitTests/TaskTestBase.cs index 607d6e5..d3ecb85 100644 --- a/src/UnitTests/TaskTestBase.cs +++ b/src/UnitTests/TaskTestBase.cs @@ -568,7 +568,9 @@ private static string GetSampleDataForType(string type) ""options"": [ { ""name"": ""Option 1"", - ""description"": ""First option"" + ""description"": ""First option"", + ""isTrue"": true, + ""number"": 100 } ] }", @@ -580,7 +582,9 @@ private static string GetSampleDataForType(string type) ""type"": ""object"", ""value"": { ""name"": ""Option 1"", - ""description"": ""First option"" + ""description"": ""First option"", + ""isTrue"": true, + ""number"": 100 } } } @@ -588,6 +592,8 @@ private static string GetSampleDataForType(string type) "yml" => @"options: - name: Option 1 description: First option + isTrue: true + number: 100 ", _ => throw new InvalidOperationException("Unknown type") }; From bd19d2e23cfdeab62ed0ce4b3aaa76b33959cdab Mon Sep 17 00:00:00 2001 From: Billy Richardson Date: Sat, 14 Sep 2024 00:04:42 -0700 Subject: [PATCH 3/3] Make test dynamic --- src/UnitTests/TaskTestBase.cs | 81 +++++++++++++----------------- src/UnitTests/UnitTests.csproj | 2 +- src/UnitTests/VirtualFileSystem.cs | 10 +--- 3 files changed, 37 insertions(+), 56 deletions(-) diff --git a/src/UnitTests/TaskTestBase.cs b/src/UnitTests/TaskTestBase.cs index d3ecb85..d739da0 100644 --- a/src/UnitTests/TaskTestBase.cs +++ b/src/UnitTests/TaskTestBase.cs @@ -478,65 +478,54 @@ public void StressTest_ShouldAddSourcePropertyManyFiles() } [TestMethod] + [DataRow("arm", new[] { "json", "yml", "arm" }, DisplayName = "ARM -> JSON -> YAML -> ARM")] + [DataRow("arm", new[] { "yml", "json", "arm" }, DisplayName = "ARM -> YAML -> JSON -> ARM")] + [DataRow("json", new[] { "arm", "yml", "json" }, DisplayName = "JSON -> ARM -> YAML -> JSON")] + [DataRow("json", new[] { "yml", "arm", "json" }, DisplayName = "JSON -> YAML -> ARM -> JSON")] + [DataRow("yml", new[] { "arm", "json", "yml" }, DisplayName = "YAML -> ARM -> JSON -> YAML")] + [DataRow("yml", new[] { "json", "arm", "yml" }, DisplayName = "YAML -> JSON -> ARM -> YAML")] [Description("Test that files are correctly translated between ARM, JSON, and YAML.")] - public void ShouldTranslateBetweenFormatsAndValidateNoDataLoss() + public void ShouldTranslateBetweenFormatsAndValidateNoDataLoss(string inputType, string[] steps) { - // Test matrix for all combinations - var testCases = new[] - { - new { InputType = "arm", Step1OutputType = "json", Step2OutputType = "yml" }, - new { InputType = "arm", Step1OutputType = "yml", Step2OutputType = "json" }, - new { InputType = "json", Step1OutputType = "arm", Step2OutputType = "yml" }, - new { InputType = "json", Step1OutputType = "yml", Step2OutputType = "arm" }, - new { InputType = "yml", Step1OutputType = "arm", Step2OutputType = "json" }, - new { InputType = "yml", Step1OutputType = "json", Step2OutputType = "arm" } - }; - - Assert.IsTrue(testCases.Length > 0); - - foreach (var testCase in testCases) - { - // Arrange: Prepare paths and sample data based on the input type. - var paths = PrepareFilePaths(testCase.InputType, testCase.Step1OutputType, testCase.Step2OutputType); + Assert.IsTrue(steps?.Length > 0); - virtualFileSystem.CreateDirectory(paths.inputDir); - virtualFileSystem.CreateDirectory(paths.step1Dir); - virtualFileSystem.CreateDirectory(paths.step2Dir); - virtualFileSystem.CreateDirectory(paths.finalDir); + // Arrange: Prepare paths and sample data based on the input type. + var inputDir = $"{testPath}\\input"; + virtualFileSystem.CreateDirectory(inputDir); - virtualFileSystem.WriteAllText(paths.inputFilePath, GetSampleDataForType(testCase.InputType)); + // Write the initial input file + var inputFilePath = $"{inputDir}\\input.{(inputType == "arm" ? "json" : inputType)}"; + virtualFileSystem.WriteAllText(inputFilePath, GetSampleDataForType(inputType)); - // Step 1: Convert Input -> Step 1 Output - ExecuteTranslationTask(testCase.InputType, testCase.Step1OutputType, paths.inputDir, paths.step1OutputPath); + string previousInputPath = inputFilePath; + string previousOutputType = inputType; - // Step 2: Convert Step 1 Output -> Step 2 Output - ExecuteTranslationTask(testCase.Step1OutputType, testCase.Step2OutputType, paths.step1Dir, paths.step2OutputPath); + // Execute the translation steps dynamically + for (int i = 0; i < steps.Length; i++) + { + var outputType = steps[i]; + var stepDir = $"{testPath}\\step{i + 1}"; + var stepOutputPath = $"{stepDir}\\output.{(outputType == "arm" ? "json" : outputType)}"; - // Final Step: Convert Step 2 Output -> Original Input Type - ExecuteTranslationTask(testCase.Step2OutputType, testCase.InputType, paths.step2Dir, paths.finalOutputPath); + virtualFileSystem.CreateDirectory(stepDir); - // Assert: Compare final output with original input to check no data loss - AssertNoDataLoss(paths.inputFilePath, paths.finalOutputPath, testCase.InputType); + // Execute translation for this step + ExecuteTranslationTask(previousOutputType, outputType, previousInputPath, stepOutputPath); - // Clear the virtual file system for the next run - ((VirtualFileSystem)virtualFileSystem).FormatSystem(); + // Update paths for the next iteration + previousInputPath = stepOutputPath; + previousOutputType = outputType; } - } - private (string inputDir, string step1Dir, string step2Dir, string finalDir, string inputFilePath, string step1OutputPath, string step2OutputPath, string finalOutputPath) - PrepareFilePaths(string inputType, string step1OutputType, string step2OutputType) - { - string inputDir = $"{testPath}\\input"; - string step1Dir = $"{testPath}\\step1"; - string step2Dir = $"{testPath}\\step2"; - string finalDir = $"{testPath}\\final"; + // Final step: Convert the final output back to the original input type + var finalDir = $"{testPath}\\final"; + var finalOutputPath = $"{finalDir}\\final_output.{(inputType == "arm" ? "json" : inputType)}"; + virtualFileSystem.CreateDirectory(finalDir); - string inputFilePath = $"{inputDir}\\input.{(inputType == "arm" ? "json" : inputType)}"; - string step1OutputPath = $"{step1Dir}\\step1_output.{(step1OutputType == "arm" ? "json" : step1OutputType)}"; - string step2OutputPath = $"{step2Dir}\\step2_output.{(step2OutputType == "arm" ? "json" : step2OutputType)}"; - string finalOutputPath = $"{finalDir}\\final_output.{(inputType == "arm" ? "json" : inputType)}"; + ExecuteTranslationTask(previousOutputType, inputType, previousInputPath, finalOutputPath); - return (inputDir, step1Dir, step2Dir, finalDir, inputFilePath, step1OutputPath, step2OutputPath, finalOutputPath); + // Assert: Compare final output with original input to check no data loss + AssertNoDataLoss(inputFilePath, finalOutputPath, inputType); } private void ExecuteTranslationTask(string inputType, string outputType, string inputFilePath, string outputFilePath) diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index ae115c3..03b5b78 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -7,7 +7,7 @@ disable false true - CS1591,CA1707,CA5394,CA1305 + CS1591,CA1707,CA5394,CA1305,CA1861 diff --git a/src/UnitTests/VirtualFileSystem.cs b/src/UnitTests/VirtualFileSystem.cs index 973a546..f962ca6 100644 --- a/src/UnitTests/VirtualFileSystem.cs +++ b/src/UnitTests/VirtualFileSystem.cs @@ -10,7 +10,7 @@ namespace AggregateConfigBuildTask.Tests.Unit internal sealed class VirtualFileSystem(bool isWindowsMode = true) : IFileSystem { private readonly bool isWindowsMode = isWindowsMode; - private ConcurrentDictionary fileSystem = new( + private readonly ConcurrentDictionary fileSystem = new( isWindowsMode ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); private RegexOptions RegexOptions => isWindowsMode ? RegexOptions.IgnoreCase : RegexOptions.None; @@ -132,14 +132,6 @@ public Stream OpenRead(string inputPath) return new MemoryStream(byteArray); } - /// - /// Delete all files on the virtual file system. - /// - public void FormatSystem() - { - fileSystem = new ConcurrentDictionary(); - } - /// /// Ensures that the provided directory path ends with a directory separator character. ///