From 9d0b47200b19473b8d3c9382795ff92de269a9e2 Mon Sep 17 00:00:00 2001 From: Gabriel Weyer Date: Fri, 25 Oct 2024 19:41:18 +1100 Subject: [PATCH 1/5] Increase test coverage --- coster/src/AzureVmCoster/Program.cs | 40 +------- .../AzureVmCoster/Services/ArgumentReader.cs | 51 +++++++++++ .../Services/ArgumentReaderTests.cs | 91 +++++++++++++++++++ 3 files changed, 143 insertions(+), 39 deletions(-) create mode 100644 coster/src/AzureVmCoster/Services/ArgumentReader.cs create mode 100644 coster/tests/AzureVmCosterTests/Services/ArgumentReaderTests.cs diff --git a/coster/src/AzureVmCoster/Program.cs b/coster/src/AzureVmCoster/Program.cs index da99506..d223e38 100644 --- a/coster/src/AzureVmCoster/Program.cs +++ b/coster/src/AzureVmCoster/Program.cs @@ -18,8 +18,6 @@ public static async Task Main(string[] args) return -1; } - StripSurroundingDoubleQuotes(ref inputFilePath); - var inputFile = new FileInfo(inputFilePath); if (!inputFile.Exists) @@ -38,8 +36,6 @@ public static async Task Main(string[] args) if (!string.IsNullOrWhiteSpace(configurationFilePath)) { - StripSurroundingDoubleQuotes(ref configurationFilePath); - configuration = await JsonReader.DeserializeAsync(configurationFilePath) ?? new CosterConfiguration(); } @@ -78,43 +74,9 @@ private static (string? inputFilePath, string? configurationFilePath, CultureInf culture = new CultureInfo(cultureInput); } #else - for (var offset = 0; offset < args.Length; offset += 2) - { - switch (args[offset]) - { - case "-l": - case "--culture": - culture = new CultureInfo(args[offset + 1]); - break; - case "-i": - case "--input": - inputFilePath = args[offset + 1]; - break; - case "-c": - case "--configuration": - configurationFilePath = args[offset + 1]; - break; - default: - Console.WriteLine($"'{args[offset]}' is not a known switch, supported values are: '-l', '--culture', '-i', '--input', '-c', '--configuration'"); - break; - } - } + (inputFilePath, configurationFilePath, culture) = ArgumentReader.Read(args); #endif return (inputFilePath, configurationFilePath, culture); } - - /// - /// Strip starting and trailing double quotes if present. - /// When copying a path from the Explorer, Windows surrounds the path with double quotes so that it remains - /// usable if a space is present. - /// - /// The reference will be assigned to only if the path starts with ". - private static void StripSurroundingDoubleQuotes(ref string filePath) - { - if (filePath.StartsWith('"')) - { - filePath = filePath.Replace("\"", "", StringComparison.Ordinal); - } - } } diff --git a/coster/src/AzureVmCoster/Services/ArgumentReader.cs b/coster/src/AzureVmCoster/Services/ArgumentReader.cs new file mode 100644 index 0000000..c9f5cc3 --- /dev/null +++ b/coster/src/AzureVmCoster/Services/ArgumentReader.cs @@ -0,0 +1,51 @@ +namespace AzureVmCoster.Services; + +internal static class ArgumentReader +{ + public static (string? inputFilePath, string? configurationFilePath, CultureInfo culture) Read(string[] args) + { + string? inputFilePath = null; + string? configurationFilePath = null; + var culture = Thread.CurrentThread.CurrentCulture; + + for (var offset = 0; offset < args.Length; offset += 2) + { + switch (args[offset]) + { + case "-l": + case "--culture": + culture = new CultureInfo(args[offset + 1]); + break; + case "-i": + case "--input": + inputFilePath = args[offset + 1]; + StripSurroundingDoubleQuotes(ref inputFilePath); + break; + case "-c": + case "--configuration": + configurationFilePath = args[offset + 1]; + StripSurroundingDoubleQuotes(ref configurationFilePath); + break; + default: + Console.WriteLine($"'{args[offset]}' is not a known switch, supported values are: '-l', '--culture', '-i', '--input', '-c', '--configuration'"); + break; + } + } + + return (inputFilePath, configurationFilePath, culture); + } + + /// + /// Strip starting and trailing double quotes if present. + /// When copying a path from the Explorer, Windows surrounds the path with double quotes so that it remains + /// usable if a space is present. + /// + /// The reference will be assigned to only if the path starts with ". + private static void StripSurroundingDoubleQuotes(ref string filePath) + { + if (filePath.StartsWith('"')) + { + filePath = filePath.Replace("\"", "", StringComparison.Ordinal); + } + } +} diff --git a/coster/tests/AzureVmCosterTests/Services/ArgumentReaderTests.cs b/coster/tests/AzureVmCosterTests/Services/ArgumentReaderTests.cs new file mode 100644 index 0000000..d28a979 --- /dev/null +++ b/coster/tests/AzureVmCosterTests/Services/ArgumentReaderTests.cs @@ -0,0 +1,91 @@ +using System.Globalization; +using AzureVmCoster.Services; + +namespace AzureVmCosterTests.Services; + +public class ArgumentReaderTests +{ + [Fact] + public void GivenValidArguments_WhenShortNames_ThenReturnExpectedConfiguration() + { + // Arrange + var args = new[] { "-l", "en-us", "-i", "/tmp/input.json", "-c", "/tmp/config.json" }; + + // Act + var (inputFilePath, configurationFilePath, culture) = ArgumentReader.Read(args); + + // Assert + inputFilePath.Should().Be("/tmp/input.json"); + configurationFilePath.Should().Be("/tmp/config.json"); + culture.Should().Be(new CultureInfo("en-us")); + } + + [Fact] + public void GivenDoubleQuotePaths_ThenStripDoubleQuotes() + { + // Arrange + var args = new[] { "-i", @"""C:\tmp\input.json""", "-c", @"""C:\tmp\input\config.json""" }; + + // Act + var (inputFilePath, configurationFilePath, culture) = ArgumentReader.Read(args); + + // Assert + inputFilePath.Should().Be(@"C:\tmp\input.json"); + configurationFilePath.Should().Be(@"C:\tmp\input\config.json"); + } + + [Fact] + public void GivenUnknownArgument_ThenIgnoreUnknownArgument() + { + // Arrange + var args = new[] { "--unknown", "ignored", "-i", "/tmp/input.json" }; + + // Act + var (inputFilePath, _, _) = ArgumentReader.Read(args); + + // Assert + inputFilePath.Should().Be("/tmp/input.json"); + } + + [Fact] + public void GivenValidArguments_WhenLongNames_ThenReturnExpectedConfiguration() + { + // Arrange + var args = new[] { "--culture", "en-us", "--input", "/tmp/input.json", "--configuration", "/tmp/config.json" }; + + // Act + var (inputFilePath, configurationFilePath, culture) = ArgumentReader.Read(args); + + // Assert + inputFilePath.Should().Be("/tmp/input.json"); + configurationFilePath.Should().Be("/tmp/config.json"); + culture.Should().Be(new CultureInfo("en-us")); + } + + [Fact] + public void GivenNoCultureArgumentProvided_ThenUseThreadCulture() + { + // Arrange + var args = new[] { "-i", "/tmp/input.json", "-c", "/tmp/config.json" }; + + // Act + var thread = new Thread(AssertThreadCulture); + thread.Start(); + + var hasThreadTerminated = thread.Join(TimeSpan.FromSeconds(1)); + hasThreadTerminated.Should().BeTrue(); + return; + + void AssertThreadCulture() + { + Thread.CurrentThread.CurrentCulture = new CultureInfo("pl-pl"); + + var (inputFilePath, configurationFilePath, culture) = ArgumentReader.Read(args); + + // Assert + inputFilePath.Should().Be("/tmp/input.json"); + configurationFilePath.Should().Be("/tmp/config.json"); + culture.Should().Be(new CultureInfo("pl-pl")); + } + } +} From 06b4902428d1b4f39116355eb688a8631c3ffafb Mon Sep 17 00:00:00 2001 From: Gabriel Weyer Date: Sat, 26 Oct 2024 08:38:10 +1100 Subject: [PATCH 2/5] Cover input file/configuration validation --- .../Models/CosterConfiguration.cs | 14 ++++- coster/src/AzureVmCoster/Program.cs | 52 +++++++------------ .../Services/InputFileValidator.cs | 26 ++++++++++ .../Models/CosterConfigurationTests.cs | 30 +++++++++++ .../Services/InputFileValidatorTests.cs | 48 +++++++++++++++++ .../Services/InputVmParserTests.cs | 16 +++--- .../Configuration/configuration.json | 5 ++ .../input-en-au-extra-fields.csv | 0 .../input-en-au-wrong-case.csv | 0 .../{SampleInputs => Input}/input-en-au.csv | 0 .../{SampleInputs => Input}/input-fr-fr.csv | 0 11 files changed, 148 insertions(+), 43 deletions(-) create mode 100644 coster/src/AzureVmCoster/Services/InputFileValidator.cs create mode 100644 coster/tests/AzureVmCosterTests/Models/CosterConfigurationTests.cs create mode 100644 coster/tests/AzureVmCosterTests/Services/InputFileValidatorTests.cs create mode 100644 coster/tests/AzureVmCosterTests/TestFiles/Configuration/configuration.json rename coster/tests/AzureVmCosterTests/TestFiles/{SampleInputs => Input}/input-en-au-extra-fields.csv (100%) rename coster/tests/AzureVmCosterTests/TestFiles/{SampleInputs => Input}/input-en-au-wrong-case.csv (100%) rename coster/tests/AzureVmCosterTests/TestFiles/{SampleInputs => Input}/input-en-au.csv (100%) rename coster/tests/AzureVmCosterTests/TestFiles/{SampleInputs => Input}/input-fr-fr.csv (100%) diff --git a/coster/src/AzureVmCoster/Models/CosterConfiguration.cs b/coster/src/AzureVmCoster/Models/CosterConfiguration.cs index 72de602..77de939 100644 --- a/coster/src/AzureVmCoster/Models/CosterConfiguration.cs +++ b/coster/src/AzureVmCoster/Models/CosterConfiguration.cs @@ -1,6 +1,18 @@ +using AzureVmCoster.Services; + namespace AzureVmCoster.Models; internal sealed class CosterConfiguration { - public IList ExcludedVms { get; set; } = default!; + public IList ExcludedVms { get; set; } = new List(); + + public static async Task FromPathAsync(string? path) + { + if (!string.IsNullOrWhiteSpace(path)) + { + return await JsonReader.DeserializeAsync(path) ?? new CosterConfiguration(); + } + + return new CosterConfiguration(); + } } diff --git a/coster/src/AzureVmCoster/Program.cs b/coster/src/AzureVmCoster/Program.cs index d223e38..f21c7bd 100644 --- a/coster/src/AzureVmCoster/Program.cs +++ b/coster/src/AzureVmCoster/Program.cs @@ -8,49 +8,33 @@ public static class Program public static async Task Main(string[] args) { - ArgumentNullException.ThrowIfNull(args); + try + { + ArgumentNullException.ThrowIfNull(args); - var (inputFilePath, configurationFilePath, culture) = ParseConfiguration(args); + var (inputFilePath, configurationFilePath, culture) = ParseConfiguration(args); - if (string.IsNullOrWhiteSpace(inputFilePath)) - { - Console.WriteLine("The input file path is required: -i "); - return -1; - } + var inputFile = InputFileValidator.Validate(inputFilePath); + var inputVms = InputVmParser.Parse(inputFile, culture); - var inputFile = new FileInfo(inputFilePath); + var pricer = new Pricer(PricingDirectory); + pricer.EnsurePricingExists(inputVms); - if (!inputFile.Exists) - { - Console.WriteLine($"The file '{inputFile.FullName}' is not accessible"); - return -1; - } + var configuration = await CosterConfiguration.FromPathAsync(configurationFilePath); - if (!".csv".Equals(inputFile.Extension, StringComparison.OrdinalIgnoreCase)) - { - Console.WriteLine($"The file '{inputFile.FullName}' doesn't have a '.csv' extension"); - return -1; - } + var pricings = await new VmPricingParser(PricingDirectory).ParseAsync(); + pricings = Pricer.FilterPricing(pricings, configuration.ExcludedVms); - var configuration = new CosterConfiguration(); + var pricedVms = Pricer.Price(inputVms, pricings); + PricedVmWriter.Write(inputFile.Name, pricedVms, culture); - if (!string.IsNullOrWhiteSpace(configurationFilePath)) + return 0; + } + catch (Exception e) { - configuration = await JsonReader.DeserializeAsync(configurationFilePath) ?? new CosterConfiguration(); + Console.WriteLine(e); + return -1; } - - var inputVms = InputVmParser.Parse(inputFile, culture); - - var pricer = new Pricer(PricingDirectory); - pricer.EnsurePricingExists(inputVms); - - var pricings = await new VmPricingParser(PricingDirectory).ParseAsync(); - pricings = Pricer.FilterPricing(pricings, configuration.ExcludedVms); - - var pricedVms = Pricer.Price(inputVms, pricings); - PricedVmWriter.Write(inputFile.Name, pricedVms, culture); - - return 0; } private static (string? inputFilePath, string? configurationFilePath, CultureInfo culture) ParseConfiguration(string[] args) diff --git a/coster/src/AzureVmCoster/Services/InputFileValidator.cs b/coster/src/AzureVmCoster/Services/InputFileValidator.cs new file mode 100644 index 0000000..02b4233 --- /dev/null +++ b/coster/src/AzureVmCoster/Services/InputFileValidator.cs @@ -0,0 +1,26 @@ +namespace AzureVmCoster.Services; + +internal static class InputFileValidator +{ + public static FileInfo Validate(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentOutOfRangeException(nameof(path), "The input file path is required: -i "); + } + + var inputFile = new FileInfo(path); + + if (!inputFile.Exists) + { + throw new InvalidOperationException($"The file '{inputFile.FullName}' is not accessible"); + } + + if (!".csv".Equals(inputFile.Extension, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentOutOfRangeException(nameof(path), $"The file '{inputFile.FullName}' doesn't have a '.csv' extension"); + } + + return inputFile; + } +} diff --git a/coster/tests/AzureVmCosterTests/Models/CosterConfigurationTests.cs b/coster/tests/AzureVmCosterTests/Models/CosterConfigurationTests.cs new file mode 100644 index 0000000..4c6cdd8 --- /dev/null +++ b/coster/tests/AzureVmCosterTests/Models/CosterConfigurationTests.cs @@ -0,0 +1,30 @@ +namespace AzureVmCosterTests.Models; + +public class CosterConfigurationTests +{ + [Theory] + [InlineData(null)] + [InlineData("")] + public async Task GivenNoFileProvided_ThenDefaultConfiguration(string? path) + { + // Act + var actualConfiguration = await CosterConfiguration.FromPathAsync(path); + + // Assert + actualConfiguration.ExcludedVms.Should().BeEmpty(); + } + + [Fact] + public async Task GivenValidConfigurationFile_ThenReadExcludedVms() + { + // Arrange + const string path = "TestFiles/Configuration/configuration.json"; + + // Act + var actualConfiguration = await CosterConfiguration.FromPathAsync(path); + + // Assert + var expectedExcludedVms = new List { "B2ts v2" }; + actualConfiguration.ExcludedVms.Should().BeEquivalentTo(expectedExcludedVms); + } +} diff --git a/coster/tests/AzureVmCosterTests/Services/InputFileValidatorTests.cs b/coster/tests/AzureVmCosterTests/Services/InputFileValidatorTests.cs new file mode 100644 index 0000000..90a58d6 --- /dev/null +++ b/coster/tests/AzureVmCosterTests/Services/InputFileValidatorTests.cs @@ -0,0 +1,48 @@ +using AzureVmCoster.Services; + +namespace AzureVmCosterTests.Services; + +public class InputFileValidatorTests +{ + [Fact] + public void GivenValidFile_ThenReturnFile() + { + // Arrange + const string filePath = "TestFiles/SampleInputs/input-en-au.csv"; + + // Act + var actualFile = InputFileValidator.Validate(filePath); + + // Assert + actualFile.Exists.Should().BeTrue(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void GivenEmptyOrNullFilePath_ThenThrow(string? filePath) + { + // Act + Assert.Throws(() => InputFileValidator.Validate(filePath)); + } + + [Fact] + public void GivenNonCsvExtension_ThenThrow() + { + // Arrange + const string filePath = "TestFiles/SamplePricing/vm-pricing_region_operating-system.json"; + + // Act + Assert.Throws(() => InputFileValidator.Validate(filePath)); + } + + [Fact] + public void GivenNonExistingFile_ThenThrow() + { + // Arrange + const string filePath = "TestFiles/SampleInputs/missing.csv"; + + // Act + Assert.Throws(() => InputFileValidator.Validate(filePath)); + } +} diff --git a/coster/tests/AzureVmCosterTests/Services/InputVmParserTests.cs b/coster/tests/AzureVmCosterTests/Services/InputVmParserTests.cs index f6b8214..1315462 100644 --- a/coster/tests/AzureVmCosterTests/Services/InputVmParserTests.cs +++ b/coster/tests/AzureVmCosterTests/Services/InputVmParserTests.cs @@ -7,16 +7,16 @@ public class InputVmParserTests { private readonly List _expected = new List { - new InputVm {Name = "name-1", Region = "us-west", Cpu = 4, Ram = 8, OperatingSystem = "windows"}, - new InputVm {Name = "name-2", Region = "us-west-2", Cpu = 8, Ram = 16, OperatingSystem = "linux"}, - new InputVm {Name = "name-3", Region = "us-west", Cpu = 16, Ram = 32.5m, OperatingSystem = "linux"} + new() {Name = "name-1", Region = "us-west", Cpu = 4, Ram = 8, OperatingSystem = "windows"}, + new() {Name = "name-2", Region = "us-west-2", Cpu = 8, Ram = 16, OperatingSystem = "linux"}, + new() {Name = "name-3", Region = "us-west", Cpu = 16, Ram = 32.5m, OperatingSystem = "linux"} }; [Fact] public void GivenExactMatchInputAndCultureWithPeriodDecimalPoint_WhenParse_ThenPreserveOrder() { // Arrange - var file = new FileInfo("TestFiles/SampleInputs/input-en-au.csv"); + var file = new FileInfo("TestFiles/Input/input-en-au.csv"); var culture = new CultureInfo("en-au"); // Act @@ -31,7 +31,7 @@ public void GivenExactMatchInputAndCultureWithPeriodDecimalPoint_WhenParse_ThenP public void GivenExactMatchInputAndCultureWithPeriodDecimalPoint_WhenParse_ThenParseAllFields() { // Arrange - var file = new FileInfo("TestFiles/SampleInputs/input-en-au.csv"); + var file = new FileInfo("TestFiles/Input/input-en-au.csv"); var culture = new CultureInfo("en-au"); // Act @@ -46,7 +46,7 @@ public void GivenExactMatchInputAndCultureWithPeriodDecimalPoint_WhenParse_ThenP public void GivenInputWithUnknownFieldsAndCultureWithPeriodDecimalPoint_WhenParse_ThenIgnoreUnknownFields() { // Arrange - var fileInfo = new FileInfo("TestFiles/SampleInputs/input-en-au-extra-fields.csv"); + var fileInfo = new FileInfo("TestFiles/Input/input-en-au-extra-fields.csv"); var culture = new CultureInfo("en-au"); // Act @@ -61,7 +61,7 @@ public void GivenInputWithUnknownFieldsAndCultureWithPeriodDecimalPoint_WhenPars public void GivenExactMatchInputAndCultureWithCommaDecimalPoint_WhenParse_ThenParseAllFields() { // Arrange - var file = new FileInfo("TestFiles/SampleInputs/input-fr-fr.csv"); + var file = new FileInfo("TestFiles/Input/input-fr-fr.csv"); var culture = new CultureInfo("fr-fr"); // Act @@ -76,7 +76,7 @@ public void GivenExactMatchInputAndCultureWithCommaDecimalPoint_WhenParse_ThenPa public void GivenRegionAndOperatingSystemWithUnexpectedCase_WhenParse_ThenLowerCase() { // Arrange - var file = new FileInfo("TestFiles/SampleInputs/input-en-au-wrong-case.csv"); + var file = new FileInfo("TestFiles/Input/input-en-au-wrong-case.csv"); var culture = new CultureInfo("en-au"); // Act diff --git a/coster/tests/AzureVmCosterTests/TestFiles/Configuration/configuration.json b/coster/tests/AzureVmCosterTests/TestFiles/Configuration/configuration.json new file mode 100644 index 0000000..6bd31b9 --- /dev/null +++ b/coster/tests/AzureVmCosterTests/TestFiles/Configuration/configuration.json @@ -0,0 +1,5 @@ +{ + "excludedVms": [ + "B2ts v2" + ] +} diff --git a/coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au-extra-fields.csv b/coster/tests/AzureVmCosterTests/TestFiles/Input/input-en-au-extra-fields.csv similarity index 100% rename from coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au-extra-fields.csv rename to coster/tests/AzureVmCosterTests/TestFiles/Input/input-en-au-extra-fields.csv diff --git a/coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au-wrong-case.csv b/coster/tests/AzureVmCosterTests/TestFiles/Input/input-en-au-wrong-case.csv similarity index 100% rename from coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au-wrong-case.csv rename to coster/tests/AzureVmCosterTests/TestFiles/Input/input-en-au-wrong-case.csv diff --git a/coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au.csv b/coster/tests/AzureVmCosterTests/TestFiles/Input/input-en-au.csv similarity index 100% rename from coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au.csv rename to coster/tests/AzureVmCosterTests/TestFiles/Input/input-en-au.csv diff --git a/coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-fr-fr.csv b/coster/tests/AzureVmCosterTests/TestFiles/Input/input-fr-fr.csv similarity index 100% rename from coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-fr-fr.csv rename to coster/tests/AzureVmCosterTests/TestFiles/Input/input-fr-fr.csv From e8c3aefb78011397124eaf614f84eb7f99a53a9c Mon Sep 17 00:00:00 2001 From: Gabriel Weyer Date: Sat, 26 Oct 2024 08:44:47 +1100 Subject: [PATCH 3/5] Clean-up test files --- .../Services/InputFileValidatorTests.cs | 6 +++--- coster/tests/AzureVmCosterTests/Services/PricerTests.cs | 8 ++++---- .../AzureVmCosterTests/Services/VmPricingParserTests.cs | 2 +- .../vm-pricing_germany-west-central_windows.json | 0 .../SamplePricing/vm-pricing_region_operating-system.json | 3 --- 5 files changed, 8 insertions(+), 11 deletions(-) rename coster/tests/AzureVmCosterTests/TestFiles/{TestPricing => Pricing}/vm-pricing_germany-west-central_windows.json (100%) delete mode 100644 coster/tests/AzureVmCosterTests/TestFiles/SamplePricing/vm-pricing_region_operating-system.json diff --git a/coster/tests/AzureVmCosterTests/Services/InputFileValidatorTests.cs b/coster/tests/AzureVmCosterTests/Services/InputFileValidatorTests.cs index 90a58d6..34496c1 100644 --- a/coster/tests/AzureVmCosterTests/Services/InputFileValidatorTests.cs +++ b/coster/tests/AzureVmCosterTests/Services/InputFileValidatorTests.cs @@ -8,7 +8,7 @@ public class InputFileValidatorTests public void GivenValidFile_ThenReturnFile() { // Arrange - const string filePath = "TestFiles/SampleInputs/input-en-au.csv"; + const string filePath = "TestFiles/Input/input-en-au.csv"; // Act var actualFile = InputFileValidator.Validate(filePath); @@ -30,7 +30,7 @@ public void GivenEmptyOrNullFilePath_ThenThrow(string? filePath) public void GivenNonCsvExtension_ThenThrow() { // Arrange - const string filePath = "TestFiles/SamplePricing/vm-pricing_region_operating-system.json"; + const string filePath = "TestFiles/Pricing/vm-pricing_germany-west-central_windows.json"; // Act Assert.Throws(() => InputFileValidator.Validate(filePath)); @@ -40,7 +40,7 @@ public void GivenNonCsvExtension_ThenThrow() public void GivenNonExistingFile_ThenThrow() { // Arrange - const string filePath = "TestFiles/SampleInputs/missing.csv"; + const string filePath = "TestFiles/Input/missing.csv"; // Act Assert.Throws(() => InputFileValidator.Validate(filePath)); diff --git a/coster/tests/AzureVmCosterTests/Services/PricerTests.cs b/coster/tests/AzureVmCosterTests/Services/PricerTests.cs index 4c5e72f..50ae97d 100644 --- a/coster/tests/AzureVmCosterTests/Services/PricerTests.cs +++ b/coster/tests/AzureVmCosterTests/Services/PricerTests.cs @@ -5,7 +5,7 @@ namespace AzureVmCosterTests.Services; public class PricerTests { - private readonly Pricer _target = new("TestFiles/SamplePricing/"); + private readonly Pricer _target = new("TestFiles/Pricing/"); [Fact] public void GivenMissingRegionAndMissingOperatingSystem_WhenEnsurePricingExists_ThenThrow() @@ -29,7 +29,7 @@ public void GivenExistingRegionAndExistingOperatingSystem_WhenEnsurePricingExist // Arrange var vms = new List { - new() {Region = "region", OperatingSystem = "operating-system"} + new() {Region = "germany-west-central", OperatingSystem = "windows"} }; // Act @@ -45,7 +45,7 @@ public void GivenExistingRegionAndMissingOperatingSystem_WhenEnsurePricingExists // Arrange var vms = new List { - new() {Region = "region", OperatingSystem = "missing"} + new() {Region = "germany-west-central", OperatingSystem = "missing"} }; // Act @@ -61,7 +61,7 @@ public void GivenMissingRegionAndExistingOperatingSystem_WhenEnsurePricingExists // Arrange var vms = new List { - new() {Region = "missing", OperatingSystem = "operating-system"} + new() {Region = "missing", OperatingSystem = "windows"} }; // Act diff --git a/coster/tests/AzureVmCosterTests/Services/VmPricingParserTests.cs b/coster/tests/AzureVmCosterTests/Services/VmPricingParserTests.cs index d30fcc8..90d6e1e 100644 --- a/coster/tests/AzureVmCosterTests/Services/VmPricingParserTests.cs +++ b/coster/tests/AzureVmCosterTests/Services/VmPricingParserTests.cs @@ -8,7 +8,7 @@ public class VmPricingParserTests public async Task GivenValidPrice_ThenParseVm() { // Arrange - var parser = new VmPricingParser("TestFiles/TestPricing/"); + var parser = new VmPricingParser("TestFiles/Pricing/"); // Act var prices = await parser.ParseAsync(); diff --git a/coster/tests/AzureVmCosterTests/TestFiles/TestPricing/vm-pricing_germany-west-central_windows.json b/coster/tests/AzureVmCosterTests/TestFiles/Pricing/vm-pricing_germany-west-central_windows.json similarity index 100% rename from coster/tests/AzureVmCosterTests/TestFiles/TestPricing/vm-pricing_germany-west-central_windows.json rename to coster/tests/AzureVmCosterTests/TestFiles/Pricing/vm-pricing_germany-west-central_windows.json diff --git a/coster/tests/AzureVmCosterTests/TestFiles/SamplePricing/vm-pricing_region_operating-system.json b/coster/tests/AzureVmCosterTests/TestFiles/SamplePricing/vm-pricing_region_operating-system.json deleted file mode 100644 index 077404a..0000000 --- a/coster/tests/AzureVmCosterTests/TestFiles/SamplePricing/vm-pricing_region_operating-system.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} \ No newline at end of file From 207245ec6625c3019f05463301e731795a25c677 Mon Sep 17 00:00:00 2001 From: Gabriel Weyer Date: Sat, 26 Oct 2024 09:01:28 +1100 Subject: [PATCH 4/5] Fix DEBUG mode --- coster/src/AzureVmCoster/Program.cs | 2 ++ coster/src/AzureVmCoster/Services/ArgumentReader.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/coster/src/AzureVmCoster/Program.cs b/coster/src/AzureVmCoster/Program.cs index f21c7bd..fd4ee6c 100644 --- a/coster/src/AzureVmCoster/Program.cs +++ b/coster/src/AzureVmCoster/Program.cs @@ -46,9 +46,11 @@ private static (string? inputFilePath, string? configurationFilePath, CultureInf #if DEBUG Console.Write("Input file path: "); inputFilePath = Console.ReadLine(); + ArgumentReader.StripSurroundingDoubleQuotes(ref inputFilePath); Console.Write("Configuration file path (leave blank if not used): "); configurationFilePath = Console.ReadLine(); + ArgumentReader.StripSurroundingDoubleQuotes(ref configurationFilePath); Console.Write("Culture (leave blank for system default): "); var cultureInput = Console.ReadLine(); diff --git a/coster/src/AzureVmCoster/Services/ArgumentReader.cs b/coster/src/AzureVmCoster/Services/ArgumentReader.cs index c9f5cc3..ad4ed80 100644 --- a/coster/src/AzureVmCoster/Services/ArgumentReader.cs +++ b/coster/src/AzureVmCoster/Services/ArgumentReader.cs @@ -41,9 +41,9 @@ public static (string? inputFilePath, string? configurationFilePath, CultureInfo /// usable if a space is present. /// /// The reference will be assigned to only if the path starts with ". - private static void StripSurroundingDoubleQuotes(ref string filePath) + public static void StripSurroundingDoubleQuotes(ref string? filePath) { - if (filePath.StartsWith('"')) + if (filePath != null && filePath.StartsWith('"')) { filePath = filePath.Replace("\"", "", StringComparison.Ordinal); } From 17793bd8edd959778f12656d3045a8d06de57a10 Mon Sep 17 00:00:00 2001 From: Gabriel Weyer Date: Sat, 26 Oct 2024 09:07:55 +1100 Subject: [PATCH 5/5] Fix up warnings --- coster/src/AzureVmCoster/Program.cs | 2 ++ coster/tests/AzureVmCosterTests/Services/ArgumentReaderTests.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/coster/src/AzureVmCoster/Program.cs b/coster/src/AzureVmCoster/Program.cs index fd4ee6c..5c7cfb5 100644 --- a/coster/src/AzureVmCoster/Program.cs +++ b/coster/src/AzureVmCoster/Program.cs @@ -30,7 +30,9 @@ public static async Task Main(string[] args) return 0; } +#pragma warning disable CA1031 // This is a catch-all so that we can log the exception catch (Exception e) +#pragma warning restore CA1031 { Console.WriteLine(e); return -1; diff --git a/coster/tests/AzureVmCosterTests/Services/ArgumentReaderTests.cs b/coster/tests/AzureVmCosterTests/Services/ArgumentReaderTests.cs index d28a979..338c315 100644 --- a/coster/tests/AzureVmCosterTests/Services/ArgumentReaderTests.cs +++ b/coster/tests/AzureVmCosterTests/Services/ArgumentReaderTests.cs @@ -27,7 +27,7 @@ public void GivenDoubleQuotePaths_ThenStripDoubleQuotes() var args = new[] { "-i", @"""C:\tmp\input.json""", "-c", @"""C:\tmp\input\config.json""" }; // Act - var (inputFilePath, configurationFilePath, culture) = ArgumentReader.Read(args); + var (inputFilePath, configurationFilePath, _) = ArgumentReader.Read(args); // Assert inputFilePath.Should().Be(@"C:\tmp\input.json");