Files

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();
}
}
}