Template
411 lines
17 KiB
C#
411 lines
17 KiB
C#
using CsvHelper;
|
|
using CsvHelper.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Strata.ContinuousImprovement.Biz.Shared;
|
|
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
|
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess;
|
|
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.ChargeCode;
|
|
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums;
|
|
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests.DistributionProcess
|
|
{
|
|
[ExcludeFromCodeCoverage]
|
|
[TestFixture, Category("Integration"), Category("CCI")]
|
|
public class DistributionProcessChargeCodeTests : IntegrationTestBase
|
|
{
|
|
private const string SourceFilePath = @"SnowflakeIntegrationTests/DistributionProcess/TestData/ChargeCode";
|
|
|
|
private static readonly ImmutableDictionary<string, (Period<DateOnly> Source, Period<DateOnly> Target)> _dataPeriod = new Dictionary<string, (Period<DateOnly> Source, Period<DateOnly> Target)>()
|
|
{
|
|
{ "AllMonthsAreCross", (new Period<DateOnly>(new DateOnly(2022, 12, 1), new DateOnly(2023, 12, 31)), new Period<DateOnly>(new DateOnly(2024, 1, 1), new DateOnly(2024, 12, 31))) },
|
|
{ "AllMonthsAreNotCross", (new Period<DateOnly>(new DateOnly(2023, 1, 1), new DateOnly(2023, 5, 30)), new Period<DateOnly>(new DateOnly(2023, 6, 1), new DateOnly(2023, 12, 31))) },
|
|
{ "MixedMonths", (new Period<DateOnly>(new DateOnly(2022, 12, 1), new DateOnly(2023, 8, 31)), new Period<DateOnly>(new DateOnly(2023, 5, 1), new DateOnly(2023, 12, 31))) }
|
|
}.ToImmutableDictionary();
|
|
|
|
private IDistributionProcessFactory _distributionProcessFactory;
|
|
private const long TestOpportunityId = 1;
|
|
private Period<DateOnly> _baselinePeriod;
|
|
private Period<DateOnly> _goalPeriod;
|
|
private Period<DateOnly> _trackingPeriod;
|
|
private double[] _rampUps;
|
|
|
|
[OneTimeSetUp]
|
|
public async Task Setup()
|
|
{
|
|
await CleanData();
|
|
await SeedDataFromCsv("BaselineData.csv");
|
|
_distributionProcessFactory = ServiceProvider.GetRequiredService<IDistributionProcessFactory>();
|
|
_baselinePeriod = new Period<DateOnly>(new DateOnly(2023, 1, 1), new DateOnly(2023, 12, 31));
|
|
_goalPeriod = new Period<DateOnly>(new DateOnly(2024, 1, 1), new DateOnly(2024, 12, 31));
|
|
_trackingPeriod = new Period<DateOnly>(new DateOnly(2024, 1, 1), new DateOnly(2024, 12, 31));
|
|
_rampUps = new double[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0 };
|
|
}
|
|
|
|
[OneTimeTearDown]
|
|
public async Task Cleanup()
|
|
{
|
|
await CleanData();
|
|
}
|
|
|
|
[TearDown]
|
|
public async Task CleanupTestData()
|
|
{
|
|
await CleanData(false);
|
|
}
|
|
|
|
[TestCaseSource(nameof(DistributeAsync_FromSourceToOpportunity_TestCases))]
|
|
public async Task DistributeAsync_FromSourceToOpportunity
|
|
(
|
|
DistributionMethod distributionMethod,
|
|
Period<DateOnly> sourcePeriod,
|
|
Period<DateOnly> targetPeriod,
|
|
double[] rampUps
|
|
)
|
|
{
|
|
// Arrange
|
|
var distributionProcess = _distributionProcessFactory.CreateDistributionProcessChargeCode(
|
|
TestOpportunityId,
|
|
sourcePeriod ?? _baselinePeriod,
|
|
targetPeriod ?? _goalPeriod,
|
|
_trackingPeriod,
|
|
[]);
|
|
|
|
// Act
|
|
var summary = await distributionProcess.DistributeAsync(distributionMethod, DistributionStage.FromSourceToOpportunity);
|
|
|
|
// Assert
|
|
var sbResult = new StringBuilder();
|
|
var details = await GetDetailRecords(isBaseline: false);
|
|
sbResult.AppendLine(ConvertListDetailToCsvString(details));
|
|
sbResult.AppendLine(ConvertSummaryToCsvString(summary));
|
|
await Verifier.Verify(sbResult.ToString(), verifySettings)
|
|
.UseFileName(TestContext.CurrentContext.TestName());
|
|
}
|
|
|
|
[TestCaseSource(nameof(DistributeAsync_FromOpportunityToInitiative_TestCases))]
|
|
public async Task DistributeAsync_FromOpportunityToInitiative
|
|
(
|
|
DistributionMethod distributionMethod,
|
|
Period<DateOnly> sourcePeriod,
|
|
Period<DateOnly> targetPeriod,
|
|
double[] rampUps
|
|
)
|
|
{
|
|
// Arrange
|
|
await SeedDataFromCsv("GoalData.csv");
|
|
var distributionProcess = _distributionProcessFactory.CreateDistributionProcessChargeCode(
|
|
TestOpportunityId,
|
|
_baselinePeriod,
|
|
sourcePeriod ?? _goalPeriod,
|
|
targetPeriod ?? _trackingPeriod,
|
|
rampUps ?? _rampUps);
|
|
|
|
// Act
|
|
var summary = await distributionProcess.DistributeAsync(distributionMethod, DistributionStage.FromOpportunityToInitiative);
|
|
|
|
// Assert
|
|
var sbResult = new StringBuilder();
|
|
var details = await GetDetailRecords(isBaseline: false);
|
|
sbResult.AppendLine(ConvertListDetailToCsvString(details));
|
|
sbResult.AppendLine(ConvertSummaryToCsvString(summary));
|
|
await Verifier.Verify(sbResult.ToString(), verifySettings)
|
|
.UseFileName(TestContext.CurrentContext.TestName());
|
|
}
|
|
|
|
[TestCaseSource(nameof(DistributeAsync_FromInitiativeToOpportunity_TestCases))]
|
|
public async Task DistributeAsync_FromInitiativeToOpportunity
|
|
(
|
|
DistributionMethod distributionMethod,
|
|
Period<DateOnly> sourcePeriod,
|
|
Period<DateOnly> targetPeriod,
|
|
double[] rampUps
|
|
)
|
|
{
|
|
// Arrange
|
|
await SeedDataFromCsv("GoalData.csv");
|
|
var distributionProcess = _distributionProcessFactory.CreateDistributionProcessChargeCode(
|
|
TestOpportunityId,
|
|
_baselinePeriod,
|
|
sourcePeriod ?? _goalPeriod,
|
|
targetPeriod ?? _trackingPeriod,
|
|
rampUps ?? _rampUps);
|
|
|
|
// Act
|
|
var summary = await distributionProcess.DistributeAsync(distributionMethod, DistributionStage.FromInitiativeToOpportunity);
|
|
|
|
// Assert
|
|
var sbResult = new StringBuilder();
|
|
var details = await GetDetailRecords(isBaseline: false);
|
|
sbResult.AppendLine(ConvertListDetailToCsvString(details));
|
|
sbResult.AppendLine(ConvertSummaryToCsvString(summary));
|
|
await Verifier.Verify(sbResult.ToString(), verifySettings)
|
|
.UseFileName(TestContext.CurrentContext.TestName());
|
|
}
|
|
|
|
[TestCaseSource(nameof(UpdateDetailCommittedByRampUp_TestCases))]
|
|
public async Task UpdateDetailCommittedByRampUp
|
|
(
|
|
double[] oldRampUps,
|
|
double[] newRampUps
|
|
)
|
|
{
|
|
// Arrange
|
|
await SeedDataFromCsv("GoalData.csv");
|
|
var distributionProcess = _distributionProcessFactory.CreateDistributionProcessChargeCode(
|
|
TestOpportunityId,
|
|
_baselinePeriod,
|
|
_goalPeriod,
|
|
_trackingPeriod,
|
|
newRampUps ?? _rampUps);
|
|
|
|
// Act
|
|
var summary = await distributionProcess.UpdateDetailCommittedByRampUp(oldRampUps);
|
|
|
|
// Assert
|
|
var sbResult = new StringBuilder();
|
|
var details = await GetDetailRecords(isBaseline: false);
|
|
sbResult.AppendLine(ConvertListDetailToCsvString(details));
|
|
sbResult.AppendLine(ConvertSummaryToCsvString(summary));
|
|
await Verifier.Verify(sbResult.ToString(), verifySettings)
|
|
.UseFileName(TestContext.CurrentContext.TestName());
|
|
}
|
|
|
|
[TestCaseSource(nameof(GetDistributionSummary_TestCases))]
|
|
public async Task GetDistributionSummary
|
|
(
|
|
bool isInitiative,
|
|
double[] rampUps
|
|
)
|
|
{
|
|
// Arrange
|
|
await SeedDataFromCsv("GoalData.csv");
|
|
var distributionProcess = _distributionProcessFactory.CreateDistributionProcessChargeCode(
|
|
TestOpportunityId,
|
|
_baselinePeriod,
|
|
_goalPeriod,
|
|
_trackingPeriod,
|
|
rampUps ?? _rampUps);
|
|
|
|
// Act
|
|
var summary = await distributionProcess.GetDistributionSummaryAsync(isInitiative);
|
|
|
|
// Assert
|
|
var sbResult = new StringBuilder();
|
|
sbResult.AppendLine(ConvertSummaryToCsvString(summary));
|
|
await Verifier.Verify(sbResult.ToString(), verifySettings)
|
|
.UseFileName(TestContext.CurrentContext.TestName());
|
|
}
|
|
|
|
#region Test Case Sources
|
|
|
|
private static IEnumerable<TestCaseData> DistributeAsync_FromSourceToOpportunity_TestCases()
|
|
{
|
|
var distributionStageName = DistributionStage.FromSourceToOpportunity.ToString();
|
|
return DistributeAsync_TestCases(distributionStageName);
|
|
}
|
|
|
|
private static IEnumerable<TestCaseData> DistributeAsync_FromOpportunityToInitiative_TestCases()
|
|
{
|
|
var distributionStageName = DistributionStage.FromOpportunityToInitiative.ToString();
|
|
foreach (var x in DistributeAsync_TestCases(distributionStageName))
|
|
yield return x;
|
|
|
|
if (_dataPeriod.TryGetValue("AllMonthsAreCross", out var periods))
|
|
{
|
|
yield return new TestCaseData(
|
|
DistributionMethod.Average,
|
|
periods.Source,
|
|
periods.Target,
|
|
Array.Empty<double>()
|
|
).SetName($"DistributeAsync_{distributionStageName}_Average_AllMonthsAreCross_WithoutRampups");
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<TestCaseData> DistributeAsync_FromInitiativeToOpportunity_TestCases()
|
|
{
|
|
var distributionStageName = DistributionStage.FromInitiativeToOpportunity.ToString();
|
|
foreach (var x in DistributeAsync_TestCases(distributionStageName))
|
|
yield return x;
|
|
|
|
if (_dataPeriod.TryGetValue("AllMonthsAreCross", out var periods))
|
|
{
|
|
yield return new TestCaseData(
|
|
DistributionMethod.Average,
|
|
periods.Source,
|
|
periods.Target,
|
|
Array.Empty<double>()
|
|
).SetName($"DistributeAsync_{distributionStageName}_Average_AllMonthsAreCross_WithoutRampups");
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<TestCaseData> DistributeAsync_TestCases(string distributionStageName)
|
|
{
|
|
|
|
if (_dataPeriod.TryGetValue("AllMonthsAreCross", out var periods))
|
|
{
|
|
yield return new TestCaseData(
|
|
DistributionMethod.Average,
|
|
periods.Source,
|
|
periods.Target,
|
|
null
|
|
).SetName($"DistributeAsync_{distributionStageName}_Average_AllMonthsAreCross");
|
|
}
|
|
|
|
foreach (var x in _dataPeriod)
|
|
{
|
|
yield return new TestCaseData(
|
|
DistributionMethod.Monthly,
|
|
x.Value.Source,
|
|
x.Value.Target,
|
|
null
|
|
).SetName($"DistributeAsync_{distributionStageName}_Monthly_{x.Key}");
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<TestCaseData> UpdateDetailCommittedByRampUp_TestCases()
|
|
{
|
|
var rampUp = new double[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0 };
|
|
yield return new TestCaseData(
|
|
rampUp,
|
|
rampUp.Reverse().ToArray()
|
|
).SetName($"UpdateDetailCommittedByRampUp_RampUpToRampUp");
|
|
yield return new TestCaseData(
|
|
rampUp,
|
|
Array.Empty<double>()
|
|
).SetName($"UpdateDetailCommittedByRampUp_RampUpToNoRampUp");
|
|
yield return new TestCaseData(
|
|
Array.Empty<double>(),
|
|
rampUp
|
|
).SetName($"UpdateDetailCommittedByRampUp_NoRampUpToRampUp");
|
|
yield return new TestCaseData(
|
|
Array.Empty<double>(),
|
|
Array.Empty<double>()
|
|
).SetName($"UpdateDetailCommittedByRampUp_NoRampUpToNoRampUp");
|
|
yield return new TestCaseData(
|
|
rampUp,
|
|
rampUp
|
|
).SetName($"UpdateDetailCommittedByRampUp_NoChangeRampUp");
|
|
}
|
|
|
|
private static IEnumerable<TestCaseData> GetDistributionSummary_TestCases()
|
|
{
|
|
yield return new TestCaseData(
|
|
true,
|
|
null
|
|
).SetName($"GetDistributionSummary_Initiative");
|
|
yield return new TestCaseData(
|
|
true,
|
|
Array.Empty<double>()
|
|
).SetName($"GetDistributionSummary_Initiative_NoRampUp");
|
|
yield return new TestCaseData(
|
|
false,
|
|
null
|
|
).SetName($"GetDistributionSummary_Opportunity");
|
|
yield return new TestCaseData(
|
|
false,
|
|
Array.Empty<double>()
|
|
).SetName($"GetDistributionSummary_Opportunity_NoRampUp");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private async Task SeedDataFromCsv(string fileName)
|
|
{
|
|
var filePath = Path.IsPathRooted(fileName)
|
|
? fileName
|
|
: Path.Combine(AppContext.BaseDirectory, SourceFilePath, fileName);
|
|
|
|
var insertSql = await GetSqlInsertFromCsv(filePath, StrategicOpportunityService.StrategicOpportunityDetailTableName);
|
|
|
|
await SnowflakeDatabaseContext.ExecuteCommandAsync(insertSql);
|
|
}
|
|
|
|
private async Task<string> GetSqlInsertFromCsv(string filePath, string tableName)
|
|
{
|
|
if (!File.Exists(filePath))
|
|
{
|
|
throw new FileNotFoundException($"Test data file not found: {filePath}");
|
|
}
|
|
|
|
var lines = await File.ReadAllLinesAsync(filePath);
|
|
if (lines.Length < 2)
|
|
{
|
|
throw new InvalidOperationException($"CSV file {filePath} must contain a header and at least one data row");
|
|
}
|
|
|
|
var values = lines.Skip(1).Select(x => $"({x})").ToList();
|
|
|
|
var insertSql = $@"
|
|
INSERT INTO {tableName} ({lines[0]})
|
|
VALUES {string.Join(",\n", values)}
|
|
";
|
|
|
|
return insertSql;
|
|
}
|
|
|
|
private string ConvertListDetailToCsvString(IEnumerable<StrategicOpportunityDetail> records)
|
|
{
|
|
using (var writer = new StringWriter())
|
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
|
{
|
|
csv.Context.RegisterClassMap<StrategicOpportunityDetailCsvMap>();
|
|
csv.WriteRecords(records);
|
|
return writer.ToString();
|
|
}
|
|
}
|
|
|
|
private string ConvertSummaryToCsvString(DistributionChargeCodeSummary summary)
|
|
{
|
|
using (var writer = new StringWriter())
|
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
|
{
|
|
csv.WriteRecords([summary]);
|
|
return writer.ToString();
|
|
}
|
|
}
|
|
|
|
private async Task<IEnumerable<StrategicOpportunityDetail>> GetDetailRecords(bool? isBaseline = null)
|
|
{
|
|
var sql = $@"
|
|
SELECT * FROM {StrategicOpportunityService.StrategicOpportunityDetailTableName}
|
|
WHERE OpportunityId = {TestOpportunityId} {(isBaseline.HasValue ? $"AND IsBaseline = {isBaseline}" : "")}
|
|
ORDER BY DischargeFiscalYearId, DischargeFiscalMonthId, EntityId
|
|
";
|
|
var records = await SnowflakeDatabaseContext.QueryAsync<StrategicOpportunityDetail>(sql, nameof(GetDetailRecords));
|
|
return records;
|
|
}
|
|
|
|
private async Task CleanData(bool? isBaseline = null)
|
|
{
|
|
await SnowflakeDatabaseContext.ExecuteCommandAsync($@"
|
|
DELETE FROM {StrategicOpportunityService.StrategicOpportunityDetailTableName}
|
|
WHERE OpportunityId = {TestOpportunityId} {(isBaseline.HasValue ? $"AND IsBaseline = {isBaseline}" : "")}
|
|
");
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
public class StrategicOpportunityDetailCsvMap : ClassMap<StrategicOpportunityDetail>
|
|
{
|
|
public StrategicOpportunityDetailCsvMap()
|
|
{
|
|
AutoMap(CultureInfo.InvariantCulture);
|
|
Map(m => m.RowId).Ignore();
|
|
}
|
|
}
|
|
}
|