diff --git a/src/UnitTests/TaskTestBase.cs b/src/UnitTests/TaskTestBase.cs index 1b4a9ab..d739da0 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,117 @@ 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(string inputType, string[] steps) + { + Assert.IsTrue(steps?.Length > 0); + + // Arrange: Prepare paths and sample data based on the input type. + var inputDir = $"{testPath}\\input"; + virtualFileSystem.CreateDirectory(inputDir); + + // Write the initial input file + var inputFilePath = $"{inputDir}\\input.{(inputType == "arm" ? "json" : inputType)}"; + virtualFileSystem.WriteAllText(inputFilePath, GetSampleDataForType(inputType)); + + string previousInputPath = inputFilePath; + string previousOutputType = inputType; + + // 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)}"; + + virtualFileSystem.CreateDirectory(stepDir); + + // Execute translation for this step + ExecuteTranslationTask(previousOutputType, outputType, previousInputPath, stepOutputPath); + + // Update paths for the next iteration + previousInputPath = stepOutputPath; + previousOutputType = outputType; + } + + // 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); + + ExecuteTranslationTask(previousOutputType, inputType, previousInputPath, 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) + { + 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"", + ""isTrue"": true, + ""number"": 100 + } + ] +}", + "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"", + ""isTrue"": true, + ""number"": 100 + } + } + } +}", + "yml" => @"options: +- name: Option 1 + description: First option + isTrue: true + number: 100 +", + _ => throw new InvalidOperationException("Unknown type") + }; + } + /// /// Check if an option exists with a given name and source /// 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. ///