From a63536af4f92675622af8cbb0176f20f9397f806 Mon Sep 17 00:00:00 2001 From: Gabriel Weyer Date: Sun, 20 Oct 2024 10:31:59 +1100 Subject: [PATCH] Increase Coster test coverage --- coster/.editorconfig | 4 + coster/src/AzureVmCoster/Services/Pricer.cs | 2 +- .../AzureVmCosterTests.csproj | 16 +--- .../Models/FileIdentifierTests.cs | 16 ++++ .../Services/InputVmParserTests.cs | 10 +- .../Services/PricerTests.cs | 96 +++++++++++++++++-- .../Services/VmPricingParserTests.cs | 20 +++- ...-pricing_germany-west-central_windows.json | 2 + .../SampleInputs/input-en-au-extra-fields.csv | 0 .../SampleInputs/input-en-au-wrong-case.csv | 0 .../SampleInputs/input-en-au.csv | 0 .../SampleInputs/input-fr-fr.csv | 0 .../vm-pricing_region_operating-system.json | 0 ...-pricing_germany-west-central_windows.json | 0 .../TestInfrastructure/InputVmBuilder.cs | 36 +++++++ .../TestInfrastructure/VmPricingBuilder.cs | 72 ++++++++++++++ 16 files changed, 241 insertions(+), 33 deletions(-) create mode 100644 coster/tests/AzureVmCosterTests/TestFiles/EmptyPricing/vm-pricing_germany-west-central_windows.json rename coster/tests/AzureVmCosterTests/{ => TestFiles}/SampleInputs/input-en-au-extra-fields.csv (100%) rename coster/tests/AzureVmCosterTests/{ => TestFiles}/SampleInputs/input-en-au-wrong-case.csv (100%) rename coster/tests/AzureVmCosterTests/{ => TestFiles}/SampleInputs/input-en-au.csv (100%) rename coster/tests/AzureVmCosterTests/{ => TestFiles}/SampleInputs/input-fr-fr.csv (100%) rename coster/tests/AzureVmCosterTests/{ => TestFiles}/SamplePricing/vm-pricing_region_operating-system.json (100%) rename coster/tests/AzureVmCosterTests/{ => TestFiles}/TestPricing/vm-pricing_germany-west-central_windows.json (100%) diff --git a/coster/.editorconfig b/coster/.editorconfig index cf45f7b..5972e96 100644 --- a/coster/.editorconfig +++ b/coster/.editorconfig @@ -199,6 +199,10 @@ dotnet_diagnostic.CA2007.severity = none # This is not a library. The code is only used in a console app where there is no SynchronizationContext dotnet_diagnostic.MA0004.severity = none +# https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0007.md +# This is a personal preference +dotnet_diagnostic.MA0007.severity = none + # CSharp code style settings: [*.cs] # Newline settings diff --git a/coster/src/AzureVmCoster/Services/Pricer.cs b/coster/src/AzureVmCoster/Services/Pricer.cs index b37151e..de9be74 100644 --- a/coster/src/AzureVmCoster/Services/Pricer.cs +++ b/coster/src/AzureVmCoster/Services/Pricer.cs @@ -16,7 +16,7 @@ public void EnsurePricingExists(List vms) var missingFiles = vms .Select(vm => new FileIdentifier(vm.Region, vm.OperatingSystem)) .Distinct(new FileIdentifierComparer()) - .Where(fileIdentifier => !File.Exists($@"{_pricingDirectory}{fileIdentifier.PricingFilename}")) + .Where(fileIdentifier => !File.Exists($"{_pricingDirectory}{fileIdentifier.PricingFilename}")) .ToList(); if (missingFiles.Count > 0) diff --git a/coster/tests/AzureVmCosterTests/AzureVmCosterTests.csproj b/coster/tests/AzureVmCosterTests/AzureVmCosterTests.csproj index eb435fc..1fe719a 100644 --- a/coster/tests/AzureVmCosterTests/AzureVmCosterTests.csproj +++ b/coster/tests/AzureVmCosterTests/AzureVmCosterTests.csproj @@ -35,20 +35,8 @@ - - Always - - - - - - Always - - - - - - Always + + PreserveNewest diff --git a/coster/tests/AzureVmCosterTests/Models/FileIdentifierTests.cs b/coster/tests/AzureVmCosterTests/Models/FileIdentifierTests.cs index 396b9ef..0cd2810 100644 --- a/coster/tests/AzureVmCosterTests/Models/FileIdentifierTests.cs +++ b/coster/tests/AzureVmCosterTests/Models/FileIdentifierTests.cs @@ -29,4 +29,20 @@ public static void GivenValidFilename_WhenFromFileInfo_ThenExpected() actual.Should().BeEquivalentTo(expected); } + + [Theory] + [InlineData(@"E:\tmp\vm-pricing_some-region.json")] + [InlineData(@"E:\tmp\vm-pricing.json")] + [InlineData(@"E:\tmp\vm-pricing_some-region_some-operating-system.csv")] + public static void GivenInvalidFilename_WhenFromFileInfo_ThenThrows(string filePath) + { + // Arrange + var file = new FileInfo(filePath); + + // Act + var actualException = Assert.Throws(() => FileIdentifier.From(file)); + + // Assert + Assert.NotNull(actualException); + } } diff --git a/coster/tests/AzureVmCosterTests/Services/InputVmParserTests.cs b/coster/tests/AzureVmCosterTests/Services/InputVmParserTests.cs index 822f4ae..f6b8214 100644 --- a/coster/tests/AzureVmCosterTests/Services/InputVmParserTests.cs +++ b/coster/tests/AzureVmCosterTests/Services/InputVmParserTests.cs @@ -16,7 +16,7 @@ public class InputVmParserTests public void GivenExactMatchInputAndCultureWithPeriodDecimalPoint_WhenParse_ThenPreserveOrder() { // Arrange - var file = new FileInfo(@"SampleInputs/input-en-au.csv"); + var file = new FileInfo("TestFiles/SampleInputs/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(@"SampleInputs/input-en-au.csv"); + var file = new FileInfo("TestFiles/SampleInputs/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(@"SampleInputs/input-en-au-extra-fields.csv"); + var fileInfo = new FileInfo("TestFiles/SampleInputs/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(@"SampleInputs/input-fr-fr.csv"); + var file = new FileInfo("TestFiles/SampleInputs/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("SampleInputs/input-en-au-wrong-case.csv"); + var file = new FileInfo("TestFiles/SampleInputs/input-en-au-wrong-case.csv"); var culture = new CultureInfo("en-au"); // Act diff --git a/coster/tests/AzureVmCosterTests/Services/PricerTests.cs b/coster/tests/AzureVmCosterTests/Services/PricerTests.cs index 23a2483..4c5e72f 100644 --- a/coster/tests/AzureVmCosterTests/Services/PricerTests.cs +++ b/coster/tests/AzureVmCosterTests/Services/PricerTests.cs @@ -5,12 +5,7 @@ namespace AzureVmCosterTests.Services; public class PricerTests { - private readonly Pricer _target; - - public PricerTests() - { - _target = new Pricer(@"SamplePricing/"); - } + private readonly Pricer _target = new("TestFiles/SamplePricing/"); [Fact] public void GivenMissingRegionAndMissingOperatingSystem_WhenEnsurePricingExists_ThenThrow() @@ -18,7 +13,7 @@ public void GivenMissingRegionAndMissingOperatingSystem_WhenEnsurePricingExists_ // Arrange var vms = new List { - new InputVm {Region = "missing", OperatingSystem = "missing"} + new() {Region = "missing", OperatingSystem = "missing"} }; // Act @@ -34,7 +29,7 @@ public void GivenExistingRegionAndExistingOperatingSystem_WhenEnsurePricingExist // Arrange var vms = new List { - new InputVm {Region = "region", OperatingSystem = "operating-system"} + new() {Region = "region", OperatingSystem = "operating-system"} }; // Act @@ -50,7 +45,7 @@ public void GivenExistingRegionAndMissingOperatingSystem_WhenEnsurePricingExists // Arrange var vms = new List { - new InputVm {Region = "region", OperatingSystem = "missing"} + new() {Region = "region", OperatingSystem = "missing"} }; // Act @@ -66,7 +61,7 @@ public void GivenMissingRegionAndExistingOperatingSystem_WhenEnsurePricingExists // Arrange var vms = new List { - new InputVm {Region = "missing", OperatingSystem = "operating-system"} + new() {Region = "missing", OperatingSystem = "operating-system"} }; // Act @@ -136,4 +131,85 @@ public void GivenExcludeList_WhenFilterPricing_ThenRemoveInstanceWithSameNameCas // Assert filteredPrices.Should().BeEmpty(); } + + [Fact] + public void GivenMatchingPrice_WhenPrice_ThenPriceVm() + { + // Arrange + var vms = new List + { + InputVmBuilder.AsUsWestWindowsD2V3Equivalent() + }; + var prices = new List + { + VmPricingBuilder.AsUsWestWindowsD2V3() + }; + + // Act + var actualPricedVms = Pricer.Price(vms, prices); + + // Assert + var expectedPricedVms = new List { new(vms[0], prices[0]) }; + actualPricedVms.Should().BeEquivalentTo(expectedPricedVms); + } + + [Fact] + public void GivenNoMatchingPrice_WhenPrice_ThenHandleVmAsNoPrice() + { + // Arrange + var vms = new List + { + InputVmBuilder.AsUsWestWindowsD2V3Equivalent() + }; + var prices = new List(); + + // Act + var actualPricedVms = Pricer.Price(vms, prices); + + // Assert + var expectedPricedVms = new List { new(vms[0], null) }; + actualPricedVms.Should().BeEquivalentTo(expectedPricedVms); + } + + [Fact] + public void GivenVmWithoutCpuAndWithoutRam_WhenPrice_ThenUseMedianCpuAndRam() + { + // Arrange + var vmWithoutCpuWithoutRam = new InputVm + { + Name = "map-me-to-median", + OperatingSystem = "windows", + Region = "us-west" + }; + var vms = new List + { + InputVmBuilder.AsUsWestWindowsD2V3Equivalent(), + InputVmBuilder.AsUsWestWindowsD4V3Equivalent(), + InputVmBuilder.AsUsWestWindowsD8V3Equivalent(), + InputVmBuilder.AsUsWestWindowsD16V3Equivalent(), + vmWithoutCpuWithoutRam + }; + var medianPrice = VmPricingBuilder.AsUsWestWindowsD8V3(); + var prices = new List + { + VmPricingBuilder.AsUsWestWindowsD2V3(), + VmPricingBuilder.AsUsWestWindowsD4V3(), + medianPrice, + VmPricingBuilder.AsUsWestWindowsD16V3() + }; + + // Act + var actualPricedVms = Pricer.Price(vms, prices); + + // Assert + var expectedPricedVms = new List + { + new(vms[0], prices[0]), + new(vms[1], prices[1]), + new(vms[2], prices[2]), + new(vms[3], prices[3]), + new(vmWithoutCpuWithoutRam, medianPrice) + }; + actualPricedVms.Should().BeEquivalentTo(expectedPricedVms); + } } diff --git a/coster/tests/AzureVmCosterTests/Services/VmPricingParserTests.cs b/coster/tests/AzureVmCosterTests/Services/VmPricingParserTests.cs index d6c14ea..d30fcc8 100644 --- a/coster/tests/AzureVmCosterTests/Services/VmPricingParserTests.cs +++ b/coster/tests/AzureVmCosterTests/Services/VmPricingParserTests.cs @@ -4,13 +4,14 @@ namespace AzureVmCosterTests.Services; public class VmPricingParserTests { - private readonly VmPricingParser _parser = new("TestPricing/"); - [Fact] public async Task GivenValidPrice_ThenParseVm() { + // Arrange + var parser = new VmPricingParser("TestFiles/TestPricing/"); + // Act - var prices = await _parser.ParseAsync(); + var prices = await parser.ParseAsync(); // Assert var expectedPrices = new List @@ -38,4 +39,17 @@ public async Task GivenValidPrice_ThenParseVm() }; prices.Should().BeEquivalentTo(expectedPrices); } + + [Fact] + public async Task GivenEmptyPriceFile_ThenHandleGracefully() + { + // Arrange + var parser = new VmPricingParser("TestFiles/EmptyPricing/"); + + // Act + var prices = await parser.ParseAsync(); + + // Assert + prices.Should().BeEmpty(); + } } diff --git a/coster/tests/AzureVmCosterTests/TestFiles/EmptyPricing/vm-pricing_germany-west-central_windows.json b/coster/tests/AzureVmCosterTests/TestFiles/EmptyPricing/vm-pricing_germany-west-central_windows.json new file mode 100644 index 0000000..0d4f101 --- /dev/null +++ b/coster/tests/AzureVmCosterTests/TestFiles/EmptyPricing/vm-pricing_germany-west-central_windows.json @@ -0,0 +1,2 @@ +[ +] diff --git a/coster/tests/AzureVmCosterTests/SampleInputs/input-en-au-extra-fields.csv b/coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au-extra-fields.csv similarity index 100% rename from coster/tests/AzureVmCosterTests/SampleInputs/input-en-au-extra-fields.csv rename to coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au-extra-fields.csv diff --git a/coster/tests/AzureVmCosterTests/SampleInputs/input-en-au-wrong-case.csv b/coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au-wrong-case.csv similarity index 100% rename from coster/tests/AzureVmCosterTests/SampleInputs/input-en-au-wrong-case.csv rename to coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au-wrong-case.csv diff --git a/coster/tests/AzureVmCosterTests/SampleInputs/input-en-au.csv b/coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au.csv similarity index 100% rename from coster/tests/AzureVmCosterTests/SampleInputs/input-en-au.csv rename to coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-en-au.csv diff --git a/coster/tests/AzureVmCosterTests/SampleInputs/input-fr-fr.csv b/coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-fr-fr.csv similarity index 100% rename from coster/tests/AzureVmCosterTests/SampleInputs/input-fr-fr.csv rename to coster/tests/AzureVmCosterTests/TestFiles/SampleInputs/input-fr-fr.csv diff --git a/coster/tests/AzureVmCosterTests/SamplePricing/vm-pricing_region_operating-system.json b/coster/tests/AzureVmCosterTests/TestFiles/SamplePricing/vm-pricing_region_operating-system.json similarity index 100% rename from coster/tests/AzureVmCosterTests/SamplePricing/vm-pricing_region_operating-system.json rename to coster/tests/AzureVmCosterTests/TestFiles/SamplePricing/vm-pricing_region_operating-system.json diff --git a/coster/tests/AzureVmCosterTests/TestPricing/vm-pricing_germany-west-central_windows.json b/coster/tests/AzureVmCosterTests/TestFiles/TestPricing/vm-pricing_germany-west-central_windows.json similarity index 100% rename from coster/tests/AzureVmCosterTests/TestPricing/vm-pricing_germany-west-central_windows.json rename to coster/tests/AzureVmCosterTests/TestFiles/TestPricing/vm-pricing_germany-west-central_windows.json diff --git a/coster/tests/AzureVmCosterTests/TestInfrastructure/InputVmBuilder.cs b/coster/tests/AzureVmCosterTests/TestInfrastructure/InputVmBuilder.cs index 7480524..56d8f75 100644 --- a/coster/tests/AzureVmCosterTests/TestInfrastructure/InputVmBuilder.cs +++ b/coster/tests/AzureVmCosterTests/TestInfrastructure/InputVmBuilder.cs @@ -13,4 +13,40 @@ public static InputVm AsUsWestWindowsD2V3Equivalent() Cpu = 2 }; } + + public static InputVm AsUsWestWindowsD4V3Equivalent() + { + return new InputVm + { + Name = "map-me-to-d4-v3", + OperatingSystem = "windows", + Ram = 16, + Region = "us-west", + Cpu = 4 + }; + } + + public static InputVm AsUsWestWindowsD8V3Equivalent() + { + return new InputVm + { + Name = "map-me-to-d8-v3", + OperatingSystem = "windows", + Ram = 32, + Region = "us-west", + Cpu = 8 + }; + } + + public static InputVm AsUsWestWindowsD16V3Equivalent() + { + return new InputVm + { + Name = "map-me-to-d16-v3", + OperatingSystem = "windows", + Ram = 64, + Region = "us-west", + Cpu = 16 + }; + } } diff --git a/coster/tests/AzureVmCosterTests/TestInfrastructure/VmPricingBuilder.cs b/coster/tests/AzureVmCosterTests/TestInfrastructure/VmPricingBuilder.cs index 47642e3..4b66b7e 100644 --- a/coster/tests/AzureVmCosterTests/TestInfrastructure/VmPricingBuilder.cs +++ b/coster/tests/AzureVmCosterTests/TestInfrastructure/VmPricingBuilder.cs @@ -25,4 +25,76 @@ public static VmPricing AsUsWestWindowsD2V3() ThreeYearReservedWithAzureHybridBenefit = 0.68m }; } + + public static VmPricing AsUsWestWindowsD4V3() + { + return new VmPricing + { + Instance = "D4 v3", + OperatingSystem = "windows", + Ram = 16, + Region = "us-west", + VCpu = 4, + PayAsYouGo = 2.2m, + PayAsYouGoWithAzureHybridBenefit = 2.1m, + Spot = 1.8m, + SpotWithAzureHybridBenefit = 1.86m, + OneYearSavingsPlan = 1.78m, + OneYearSavingsPlanWithAzureHybridBenefit = 1.72m, + ThreeYearSavingsPlan = 1.56m, + ThreeYearSavingsPlanWithAzureHybridBenefit = 1.44m, + OneYearReserved = 1.68m, + OneYearReservedWithAzureHybridBenefit = 1.62m, + ThreeYearReserved = 1.42m, + ThreeYearReservedWithAzureHybridBenefit = 1.36m + }; + } + + public static VmPricing AsUsWestWindowsD8V3() + { + return new VmPricing + { + Instance = "D8 v3", + OperatingSystem = "windows", + Ram = 32, + Region = "us-west", + VCpu = 8, + PayAsYouGo = 4.4m, + PayAsYouGoWithAzureHybridBenefit = 4.2m, + Spot = 3.6m, + SpotWithAzureHybridBenefit = 3.72m, + OneYearSavingsPlan = 3.56m, + OneYearSavingsPlanWithAzureHybridBenefit = 3.44m, + ThreeYearSavingsPlan = 3.12m, + ThreeYearSavingsPlanWithAzureHybridBenefit = 2.88m, + OneYearReserved = 3.36m, + OneYearReservedWithAzureHybridBenefit = 3.24m, + ThreeYearReserved = 2.84m, + ThreeYearReservedWithAzureHybridBenefit = 2.72m + }; + } + + public static VmPricing AsUsWestWindowsD16V3() + { + return new VmPricing + { + Instance = "D16 v3", + OperatingSystem = "windows", + Ram = 64, + Region = "us-west", + VCpu = 16, + PayAsYouGo = 8.8m, + PayAsYouGoWithAzureHybridBenefit = 8.4m, + Spot = 7.2m, + SpotWithAzureHybridBenefit = 7.44m, + OneYearSavingsPlan = 7.12m, + OneYearSavingsPlanWithAzureHybridBenefit = 6.88m, + ThreeYearSavingsPlan = 6.24m, + ThreeYearSavingsPlanWithAzureHybridBenefit = 5.76m, + OneYearReserved = 6.72m, + OneYearReservedWithAzureHybridBenefit = 6.48m, + ThreeYearReserved = 5.68m, + ThreeYearReservedWithAzureHybridBenefit = 5.44m + }; + } }