Template
chore: deploy initial code base
This commit is contained in:
+365
@@ -0,0 +1,365 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Strata.ContinuousImprovement.Biz.ClientWarehouses;
|
||||
using Strata.ContinuousImprovement.Biz.Dtos;
|
||||
using Strata.ContinuousImprovement.Biz.GMDN;
|
||||
using Strata.ContinuousImprovement.Biz.Network;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityComments;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks;
|
||||
using Strata.ContinuousImprovement.Biz.Population;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.CentralDbIntegrationTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class TestCentralDbOrm : IntegrationTestBase
|
||||
{
|
||||
public async Task TestGeneric<T>(TestContext testContext) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
var testName = testContext.TestName(typeof(T).Name);
|
||||
var dbSet = ((DbContext)CentralDbContext).Set<T>();
|
||||
var count = await dbSet.CountAsync(CancellationToken.None);
|
||||
Assert.That(count, Is.GreaterThanOrEqualTo(0));
|
||||
if (count == 0)
|
||||
{
|
||||
if (Activator.CreateInstance(typeof(T)) is T obj)
|
||||
{
|
||||
await dbSet.AddAsync(obj, CancellationToken.None);
|
||||
await ((DbContext)CentralDbContext).SaveChangesAsync(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
var result = await dbSet.FirstOrDefaultAsync(CancellationToken.None);
|
||||
Assert.That(result, Is.Not.Null.And.InstanceOf<T>());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ILB
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TestFirst<T>(TestContext testContext) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
var testName = testContext.TestName(typeof(T).Name) + " First";
|
||||
var dbSet = ((DbContext)CentralDbContext).Set<T>();
|
||||
var any = await dbSet.AnyAsync(CancellationToken.None);
|
||||
if (!any)
|
||||
{
|
||||
if (Activator.CreateInstance(typeof(T)) is T obj)
|
||||
{
|
||||
await dbSet.AddAsync(obj, CancellationToken.None);
|
||||
await ((DbContext)CentralDbContext).SaveChangesAsync(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
var result = await dbSet.FirstOrDefaultAsync(CancellationToken.None);
|
||||
Assert.That(result, Is.Not.Null.And.InstanceOf<T>());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ILB
|
||||
}
|
||||
}
|
||||
|
||||
public async Task TestCount<T>(TestContext testContext) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
var testName = testContext.TestName(typeof(T).Name) + " Count";
|
||||
var dbSet = ((DbContext)CentralDbContext).Set<T>();
|
||||
var count = await dbSet.CountAsync(CancellationToken.None);
|
||||
Assert.That(count, Is.GreaterThanOrEqualTo(0));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ILB
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(TestCaseSources))]
|
||||
public async Task TestOrmCases(string typeName, bool first)
|
||||
{
|
||||
var entityType = ((DbContext)CentralDbContext).Model.GetEntityTypes()
|
||||
.SingleOrDefault(et => et.ClrType.Name == typeName);
|
||||
if (entityType == null) { return; }
|
||||
if (first) { FirstCaseTests(typeName); }
|
||||
CountCaseTests(typeName);
|
||||
}
|
||||
|
||||
public async Task FirstCaseTests(string typeName)
|
||||
{
|
||||
switch (typeName)
|
||||
{
|
||||
case nameof(ClientNetworkOpportunity):
|
||||
await TestFirst<ClientNetworkOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ClientNetworkOpportunityLink):
|
||||
await TestFirst<ClientNetworkOpportunityLink>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ClientWarehouse):
|
||||
await TestFirst<ClientWarehouse>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(Comment):
|
||||
await TestFirst<Comment>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DeviceIdentifierDimension):
|
||||
await TestFirst<DeviceIdentifierDimension>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DeviceIdentifierTerm):
|
||||
await TestFirst<DeviceIdentifierTerm>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GMDNCategory):
|
||||
await TestFirst<GMDNCategory>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GMDNTerm):
|
||||
await TestFirst<GMDNTerm>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GMDNTermCategory):
|
||||
await TestFirst<GMDNTermCategory>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GUDIDDevice):
|
||||
await TestFirst<GUDIDDevice>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GUDIDIdentifier):
|
||||
await TestFirst<GUDIDIdentifier>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GUDIDTerm):
|
||||
await TestFirst<GUDIDTerm>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(NetworkOpportunity):
|
||||
await TestFirst<NetworkOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(OpportunityTypeWorkbook):
|
||||
await TestFirst<OpportunityTypeWorkbook>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(OpportunityWorkbook):
|
||||
await TestFirst<OpportunityWorkbook>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(TmpGUDIDIdentifier):
|
||||
await TestFirst<TmpGUDIDIdentifier>(TestContext.CurrentContext);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(typeName);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CountCaseTests(string typeName)
|
||||
{
|
||||
switch (typeName)
|
||||
{
|
||||
case nameof(ClientNetworkOpportunity):
|
||||
await TestCount<ClientNetworkOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ClientNetworkOpportunityLink):
|
||||
await TestCount<ClientNetworkOpportunityLink>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ClientWarehouse):
|
||||
await TestCount<ClientWarehouse>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(Comment):
|
||||
await TestCount<Comment>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DeviceIdentifierDimension):
|
||||
await TestCount<DeviceIdentifierDimension>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DeviceIdentifierTerm):
|
||||
await TestCount<DeviceIdentifierTerm>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GMDNCategory):
|
||||
await TestCount<GMDNCategory>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GMDNTerm):
|
||||
await TestCount<GMDNTerm>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GMDNTermCategory):
|
||||
await TestCount<GMDNTermCategory>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GUDIDDevice):
|
||||
await TestCount<GUDIDDevice>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GUDIDIdentifier):
|
||||
await TestCount<GUDIDIdentifier>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GUDIDTerm):
|
||||
await TestCount<GUDIDTerm>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(NetworkOpportunity):
|
||||
await TestCount<NetworkOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(OpportunityTypeWorkbook):
|
||||
await TestCount<OpportunityTypeWorkbook>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(OpportunityWorkbook):
|
||||
await TestCount<OpportunityWorkbook>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(TmpGUDIDIdentifier):
|
||||
await TestCount<TmpGUDIDIdentifier>(TestContext.CurrentContext);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(typeName);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task MissingCentralDbContextTestCases()
|
||||
{
|
||||
var types = ((DbContext)CentralDbContext).Model.GetEntityTypes()
|
||||
.Select(e => e.ClrType).OrderBy(c => c.Name).ToList();
|
||||
var missingTests = TestCaseSources()
|
||||
.Select(tc => tc.Arguments.First().ToString())
|
||||
.Where(tc => !types.Select(t => t.Name).Contains(tc));
|
||||
foreach (var test in missingTests)
|
||||
{
|
||||
try
|
||||
{
|
||||
throw new NotImplementedException(test);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ILB
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CentralDbContextTestCases()
|
||||
{
|
||||
var types = ((DbContext)CentralDbContext).Model.GetEntityTypes()
|
||||
.Select(e => e.ClrType).OrderBy(c => c.Name).ToList();
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
switch (type.Name)
|
||||
{
|
||||
case nameof(ClientNetworkOpportunity):
|
||||
await TestGeneric<ClientNetworkOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ClientNetworkOpportunityLink):
|
||||
await TestGeneric<ClientNetworkOpportunityLink>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ClientWarehouse):
|
||||
await TestGeneric<ClientWarehouse>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(Comment):
|
||||
await TestGeneric<Comment>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DeviceIdentifierDimension):
|
||||
await TestGeneric<DeviceIdentifierDimension>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DeviceIdentifierTerm):
|
||||
await TestGeneric<DeviceIdentifierTerm>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GMDNCategory):
|
||||
await TestGeneric<GMDNCategory>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GMDNTerm):
|
||||
await TestGeneric<GMDNTerm>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GMDNTermCategory):
|
||||
await TestGeneric<GMDNTermCategory>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GUDIDDevice):
|
||||
await TestGeneric<GUDIDDevice>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GUDIDIdentifier):
|
||||
await TestGeneric<GUDIDIdentifier>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GUDIDTerm):
|
||||
await TestGeneric<GUDIDTerm>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(NetworkOpportunity):
|
||||
await TestGeneric<NetworkOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(OpportunityTypeWorkbook):
|
||||
await TestGeneric<OpportunityTypeWorkbook>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(OpportunityWorkbook):
|
||||
await TestGeneric<OpportunityWorkbook>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(TmpGUDIDIdentifier):
|
||||
await TestGeneric<TmpGUDIDIdentifier>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ExplorationInclusionExclusion):
|
||||
await TestGeneric<ExplorationInclusionExclusion>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ExplorationPopulation):
|
||||
await TestGeneric<ExplorationPopulation>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ExplorationPopulationEncounter):
|
||||
await TestGeneric<ExplorationPopulationEncounter>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(StrategicOpportunity):
|
||||
await TestGeneric<StrategicOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(StrategicOpportunityChargeCode):
|
||||
await TestGeneric<StrategicOpportunityChargeCode>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(StrategicOpportunityGL):
|
||||
await TestGeneric<StrategicOpportunityGL>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(StrategicStakeholdersLink):
|
||||
await TestGeneric<StrategicStakeholdersLink>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(StrategicOpportunityMeasure):
|
||||
await TestGeneric<StrategicOpportunityMeasure>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(StrategicOpportunityWorkbook):
|
||||
await TestGeneric<StrategicOpportunityWorkbook>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(StrategicOpportunityRampUp):
|
||||
await TestGeneric<StrategicOpportunityRampUp>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(OpportunityRoleAssignment.OpportunityRoleAssignment):
|
||||
await TestGeneric<OpportunityRoleAssignment.OpportunityRoleAssignment>(TestContext.CurrentContext);
|
||||
break;
|
||||
default:
|
||||
var pathArr = TestContext.CurrentContext.TestDirectory.Split(Path.DirectorySeparatorChar);
|
||||
var path = string.Join(Path.DirectorySeparatorChar, pathArr[..(pathArr.Length - 3)].Append("CentralDbIntegrationTests"));
|
||||
var fileName = Path.Combine(path, $"{nameof(TestCentralDbOrm)}_{type.Name}.cs");
|
||||
if (File.Exists(fileName)) break;
|
||||
var testFile = @"
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.CentralDbIntegrationTests
|
||||
{
|
||||
[TestFixture, Category(""Integration""), Category(""CCI"")]
|
||||
public partial class TestCentralDbContext : IntegrationTestBase
|
||||
{
|
||||
[Test]
|
||||
public async Task TestCentralDb" + type.Name + @"()
|
||||
{
|
||||
await TestGeneric<" + type.Name + @">(TestContext.CurrentContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
";
|
||||
File.WriteAllText(fileName, testFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ClientNetworkOpportunityTest()
|
||||
{
|
||||
var opp = await CentralDbContext.ClientNetworkOpportunities.FirstOrDefaultAsync(CancellationToken.None);
|
||||
opp ??= new ClientNetworkOpportunity();
|
||||
opp.NetworkOpportunity ??= new NetworkOpportunity
|
||||
{
|
||||
Name = nameof(ClientNetworkOpportunityTest),
|
||||
Description = nameof(ClientNetworkOpportunityTest),
|
||||
WorkbookTemplateId = 0
|
||||
};
|
||||
var result = new ClientNetworkOpportunityDto(opp);
|
||||
Assert.That(result, Is.Not.Null.And.InstanceOf<ClientNetworkOpportunityDto>()
|
||||
.And.Property(nameof(ClientNetworkOpportunityDto.Name)).Not.Null
|
||||
.And.Property(nameof(ClientNetworkOpportunityDto.Description)).Not.Null
|
||||
.And.Property(nameof(ClientNetworkOpportunityDto.WorkbookTemplateId)).Not.Null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using Strata.ContinuousImprovement.Biz.ClientWarehouses;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts;
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.ContinuousImprovementServiceTests.ClientWarehouses
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class TestClientWarehouseService : IntegrationTestBase
|
||||
{
|
||||
private IClientWarehouseService _clientWarehouseService;
|
||||
private List<ClientWarehouse> _clientWarehouses;
|
||||
private ClientWarehouse _clientWarehouse = new ClientWarehouse
|
||||
{
|
||||
DatabaseGuid = Guid.NewGuid(),
|
||||
ClientName = "client",
|
||||
WarehouseSize = WarehouseSize.S
|
||||
};
|
||||
|
||||
[OneTimeSetUp]
|
||||
public async Task SetupServices()
|
||||
{
|
||||
_clientWarehouseService = new ClientWarehouseService((CentralDbContext)CentralDbContext);
|
||||
_clientWarehouses = new List<ClientWarehouse>();
|
||||
for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
var warehouse = new ClientWarehouse
|
||||
{
|
||||
DatabaseGuid = Guid.NewGuid(),
|
||||
ClientName = $"client_{i}",
|
||||
WarehouseSize = WarehouseSize.S
|
||||
};
|
||||
warehouse.ClientWarehouseId = await _clientWarehouseService.Add(warehouse, CancellationToken.None);
|
||||
_clientWarehouses.Add(warehouse);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestAdd()
|
||||
{
|
||||
var id = await _clientWarehouseService.Add(_clientWarehouse, CancellationToken.None);
|
||||
var warehousesExists = await _clientWarehouseService.Exists(_clientWarehouse.DatabaseGuid, CancellationToken.None);
|
||||
Assert.That(warehousesExists, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestGetByDatabaseGuid()
|
||||
{
|
||||
var warehouseClient = new ClientWarehouse
|
||||
{
|
||||
DatabaseGuid = Guid.NewGuid(),
|
||||
ClientName = "client",
|
||||
WarehouseSize = WarehouseSize.S
|
||||
};
|
||||
var id = await _clientWarehouseService.Add(warehouseClient, CancellationToken.None);
|
||||
var warehouse = await _clientWarehouseService.GetByDatabaseGuid(warehouseClient.DatabaseGuid, CancellationToken.None);
|
||||
Assert.That(warehouse, Is.Not.Null.And.InstanceOf<ClientWarehouse>()
|
||||
.And.Property(nameof(ClientWarehouse.ClientName)).EqualTo(warehouseClient.ClientName));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestGetAll()
|
||||
{
|
||||
var testName = TestContext.CurrentContext.TestName();
|
||||
var warehouses = await _clientWarehouseService.GetAll(CancellationToken.None);
|
||||
var intersect = _clientWarehouses.Intersect(warehouses);
|
||||
Assert.That(intersect, Is.Not.Null.And.Not.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestDelete()
|
||||
{
|
||||
var testName = TestContext.CurrentContext.TestName();
|
||||
var warehouse = new ClientWarehouse
|
||||
{
|
||||
DatabaseGuid = Guid.NewGuid(),
|
||||
ClientName = "client",
|
||||
WarehouseSize = WarehouseSize.S
|
||||
};
|
||||
var id = await _clientWarehouseService.Add(warehouse, CancellationToken.None);
|
||||
var warehousesExists = await _clientWarehouseService.Exists(warehouse.DatabaseGuid, CancellationToken.None);
|
||||
Assert.That(warehousesExists, Is.True);
|
||||
var warehouseDeleted = await _clientWarehouseService.Delete(warehouse.DatabaseGuid, CancellationToken.None);
|
||||
warehousesExists = await _clientWarehouseService.Exists(warehouse.DatabaseGuid, CancellationToken.None);
|
||||
Assert.That(!warehousesExists, Is.EqualTo(warehouseDeleted));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using Strata.DecisionSupport.Models.Queries;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.ContinuousImprovementServiceTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
internal class TestClientService : IntegrationTestBase
|
||||
{
|
||||
|
||||
[Test, Ignore("Client is not authorized. Need to revisit when we can auth the user performing tests.")]
|
||||
public async Task ServiceTest()
|
||||
{
|
||||
var queryInfoStr = @"{""HasManufacturerClientDSS"":false,""IsManufacturerInferred"":true,""HasSupplyClientDSS"":false,""IsSupplyInferred"":true,""IsUsingCustomEntityDimension"":false,""IsBaseline"":true,""OpportunityGuid"":""72ff5c93-f9a8-444c-a4a0-cd61c19334fa"",""ChargeCodeIds"":[],""First"":0,""Rows"":10000}";
|
||||
var queryInfo = JsonSerializer.Deserialize<EncounterChargeQueryInfo>(queryInfoStr);
|
||||
var result = await ContinuousImprovementService.DecisionSupportEncounterChargeInfo(queryInfo, CancellationToken.None);
|
||||
|
||||
await Verifier.Verify(result, verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static Strata.ContinuousImprovement.Biz.Exploration.ExplorationService;
|
||||
using ExplorationFilter = Strata.ContinuousImprovement.Biz.Exploration.Filters.Filter;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration")]
|
||||
public class ExplorationFilterServiceIntegrationTests : IntegrationTestBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Integration test for BuildDataFiltersAsync with real database context.
|
||||
///
|
||||
/// POTENTIAL FAILURE REASON: Database connection issues or missing test data.
|
||||
///
|
||||
/// TO FIX:
|
||||
/// 1. Ensure test database is properly configured and accessible
|
||||
/// 2. Verify test data exists in the database
|
||||
/// 3. Check connection strings in test configuration
|
||||
/// 4. Consider using test data seeding for consistent results
|
||||
/// </summary>
|
||||
[Test, Category("Integration")]
|
||||
public async Task BuildDataFiltersAsync_WithRealDatabaseContext_HandlesFilterChipProcessing()
|
||||
{
|
||||
// Arrange
|
||||
var filter = new ExplorationFilter
|
||||
{
|
||||
FilterChipItems = new List<FilterChipItem>
|
||||
{
|
||||
new FilterChipItem
|
||||
{
|
||||
ChipType = ChipType.CostDriver,
|
||||
Key = ChipKey.CostDriver,
|
||||
Operator = OperatorType.In,
|
||||
Filters = new List<TreeNode>
|
||||
{
|
||||
new TreeNode { Id = "LOS", DisplayText = "LOS" },
|
||||
new TreeNode { Id = "Pharmacy", DisplayText = "Pharmacy" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await ExplorationFilterService.BuildDataFiltersAsync(
|
||||
null, null, filter, false, false, false, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.SphChargeCodeCostDriver, Contains.Item("LOS"));
|
||||
Assert.That(result.SphChargeCodeCostDriver, Contains.Item("Pharmacy"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Integration test for GetFilterStringsAsync with real database context.
|
||||
///
|
||||
/// POTENTIAL FAILURE REASON: Database schema changes or missing stored procedures.
|
||||
///
|
||||
/// TO FIX:
|
||||
/// 1. Verify database schema matches expected structure
|
||||
/// 2. Ensure all required stored procedures exist
|
||||
/// 3. Check for recent database migrations
|
||||
/// 4. Update test expectations to match current schema
|
||||
/// </summary>
|
||||
[Test, Category("Integration")]
|
||||
public async Task GetFilterStringsAsync_WithRealDatabaseContext_GeneratesValidSqlFilters()
|
||||
{
|
||||
// Arrange
|
||||
var filters = new List<FilterChipItem>
|
||||
{
|
||||
new FilterChipItem
|
||||
{
|
||||
ChipType = ChipType.Boolean,
|
||||
Key = ChipKey.IsGreaterThanZeroCharges,
|
||||
IsSelected = true,
|
||||
Operator = OperatorType.In
|
||||
},
|
||||
new FilterChipItem
|
||||
{
|
||||
ChipType = ChipType.DateRange,
|
||||
Key = ChipKey.DischargeDateID,
|
||||
DateRange = new List<DateTime>
|
||||
{
|
||||
new DateTime(2024, 1, 1),
|
||||
new DateTime(2024, 12, 31)
|
||||
}
|
||||
}
|
||||
};
|
||||
var queryParams = new QueryParams { CodeType = CaseTypeCategories.MSDRG };
|
||||
|
||||
// Act
|
||||
var result = await ExplorationFilterService.GetFilterStringsAsync(filters, queryParams, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.QueryFilters, Does.StartWith("WHERE"));
|
||||
Assert.That(result.QueryFilters, Does.Contain("el.Charges > 0"));
|
||||
Assert.That(queryParams.DischargeDateStart, Is.EqualTo("2024-01-01"));
|
||||
Assert.That(queryParams.DischargeDateEnd, Is.EqualTo("2025-01-01"));
|
||||
}
|
||||
|
||||
[Test, Category("Integration")]
|
||||
public void CreateCostDriverFilter_GeneratesCompleteFilterList()
|
||||
{
|
||||
// Act
|
||||
var filter = ExplorationFilterService.CreateCostDriverFilter();
|
||||
|
||||
// Assert
|
||||
Assert.That(filter, Is.Not.Null);
|
||||
Assert.That(filter.ChipType, Is.EqualTo(ChipType.CostDriver));
|
||||
Assert.That(filter.Filters.Count(), Is.EqualTo(7)); // Should have all 7 valid SPH cost drivers
|
||||
|
||||
var expectedCostDrivers = new[]
|
||||
{
|
||||
"Anesthesia and Recovery", "Imaging", "Laboratory",
|
||||
"LOS", "OR Time", "Pharmacy", "Supplies"
|
||||
};
|
||||
|
||||
foreach (var expectedDriver in expectedCostDrivers)
|
||||
{
|
||||
Assert.That(filter.Filters.Any(f => f.Id == expectedDriver),
|
||||
Is.True, $"Missing expected cost driver: {expectedDriver}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Snowflake.Data.Client;
|
||||
using Strata.ContinuousImprovement.Biz.ClientWarehouses;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration;
|
||||
using Strata.ContinuousImprovement.Biz.FiscalMonths;
|
||||
using Strata.ContinuousImprovement.Biz.GMDN;
|
||||
using Strata.ContinuousImprovement.Biz.Network;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityComments;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks;
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.ContinuousImprovement.Client;
|
||||
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.Schema.Client;
|
||||
using Strata.SqlTools.Configuration.Common.AsyncFactory;
|
||||
using Strata.StrataSphereCompare.Client;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[SetUpFixture]
|
||||
public class IntegrationTestBase
|
||||
{
|
||||
protected VerifySettings verifyScrubbedSettings;
|
||||
protected VerifySettings verifySettings;
|
||||
protected VerifySettings verifySqlSettings;
|
||||
protected VerifySettings verifyErrorSettings;
|
||||
|
||||
public const string Quick = "quick";
|
||||
public const string ContinuousImprovementEndpoint = "https://continuousimprovement-api.dev.stratanetwork.net";
|
||||
|
||||
protected readonly Guid GmAutomationDatabase = new("cda90beb-6c2e-41af-9083-857a09fe8183"); // GM Automation (Refreshed)
|
||||
|
||||
protected Guid VariationOpportunityGuid { get; set; }
|
||||
protected Guid LOSOpportunityGuid { get; set; }
|
||||
protected ICentralDbContext CentralDbContext { get; set; }
|
||||
protected JazzEntityFrameworkFactory JazzConnBuilderFactory { get; set; }
|
||||
#pragma warning disable NUnit1032
|
||||
protected IJazzDbContext JazzDbContext { get; set; }
|
||||
protected ISnowflakeDatabaseContext SnowflakeDatabaseContext;
|
||||
protected IServiceProvider ServiceProvider; //used to grab any necessary DI objects
|
||||
#pragma warning restore NUnit1032
|
||||
protected ILogger<IntegrationTestBase>? logger;
|
||||
protected IContinuousImprovementService ContinuousImprovementService;
|
||||
protected IExplorationService ExplorationService;
|
||||
protected ISchemaServiceClient SchemaServiceClient;
|
||||
protected IExplorationFilterService ExplorationFilterService; // ADD THIS LINE
|
||||
protected ILogger<ContinuousImprovementService> ServiceLogger;
|
||||
internal IClaimsPrincipalAccessor ClaimsPrincipalAccessor { get; set; }
|
||||
|
||||
[OneTimeSetUp]
|
||||
public virtual async Task SetDbContextAndServices()
|
||||
{
|
||||
#if DEBUG
|
||||
//When running locally sets the profile to the CI profile so you have access to resources
|
||||
Environment.SetEnvironmentVariable("AWS_Profile", "sdt-continuous-improvement-service-role");
|
||||
#endif
|
||||
var services = ServiceCollectionFactory.Create();
|
||||
services.AddScoped<ISnowflakeDatabaseContext, SnowflakeDatabaseContext>(provider =>
|
||||
{
|
||||
ISnowflakeDatabaseContextFactory snowflakeDatabaseContextFactory = provider.GetRequiredService<ISnowflakeDatabaseContextFactory>();
|
||||
return snowflakeDatabaseContextFactory.Create($"{GmAutomationDatabase:N}");
|
||||
});
|
||||
ServiceProvider = services.BuildServiceProvider();
|
||||
var sfConnBuilderFactory = ServiceProvider.GetRequiredService<ISnowflakeDatabaseContextFactory>();
|
||||
SnowflakeDatabaseContext = sfConnBuilderFactory.Create($"{GmAutomationDatabase:N}");
|
||||
|
||||
try
|
||||
{
|
||||
var jazzConnBuilderFactory = ServiceProvider.GetRequiredService<IAsyncDbContextFactory<JazzDbContext>>();
|
||||
JazzDbContext = await jazzConnBuilderFactory.CreateDbContextAsync(CancellationToken.None);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ILB
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CentralDbContext = ServiceProvider.GetService<CentralDbContext>()
|
||||
?? throw new NullReferenceException(nameof(DbContexts.CentralDbContext));
|
||||
await ((CentralDbContext)CentralDbContext).Database.MigrateAsync(CancellationToken.None);
|
||||
JazzConnBuilderFactory = new JazzEntityFrameworkFactory();
|
||||
await JazzConnBuilderFactory.EnsureCreatedAsync(CentralDbContext, JazzDbContext, CancellationToken.None);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
#region Verifier Settings
|
||||
|
||||
verifySettings = TestExtensions.TestSettings();
|
||||
verifySqlSettings = TestExtensions.TestSettings();
|
||||
verifySqlSettings
|
||||
.ScrubLinesWithReplace(_ =>
|
||||
{
|
||||
var regex = new Regex(@"(.*)\s+in\s+\([\d,]*\)(.*)", RegexOptions.IgnoreCase);
|
||||
if (regex.IsMatch(_))
|
||||
{
|
||||
return regex.Replace(_, "$1 IN (...)$2");
|
||||
}
|
||||
var regexDate = new Regex(@"(.*)'[\d]{1,2}/[\d]{1,2}/[\d]{4}'(.*)");
|
||||
if (regexDate.IsMatch(_))
|
||||
{
|
||||
var line = _;
|
||||
while (regexDate.IsMatch(line))
|
||||
{
|
||||
line = regexDate.Replace(line, "$1'mm/dd/yyyy'$2");
|
||||
}
|
||||
return line;
|
||||
}
|
||||
return _;
|
||||
});
|
||||
verifyScrubbedSettings = TestExtensions.TestSettings();
|
||||
verifyScrubbedSettings
|
||||
.ScrubLinesWithReplace(replaceLine: _ => $"'{"".PadLeft(_.Length, '~')}'");
|
||||
verifyScrubbedSettings
|
||||
.ScrubMembersWithType<bool>();
|
||||
verifyScrubbedSettings
|
||||
.ScrubMembersWithType<short>();
|
||||
verifyScrubbedSettings
|
||||
.ScrubMembersWithType<int>();
|
||||
verifyScrubbedSettings
|
||||
.ScrubMembersWithType<long>();
|
||||
verifyScrubbedSettings
|
||||
.ScrubMembersWithType<byte>();
|
||||
verifyScrubbedSettings
|
||||
.ScrubMembersWithType<decimal>();
|
||||
verifyScrubbedSettings
|
||||
.ScrubMembersWithType<double>();
|
||||
verifyErrorSettings = TestExtensions.TestSettings();
|
||||
verifyErrorSettings.UseDirectory("errorSnapshots");
|
||||
#endregion
|
||||
|
||||
SchemaServiceClient = Mock.Of<ISchemaServiceClient>();
|
||||
ExplorationFilterService = new ExplorationFilterService(
|
||||
ServiceProvider.GetRequiredService<IAsyncDbContextFactory<JazzDbContext>>(),
|
||||
SchemaServiceClient,
|
||||
Mock.Of<ILogger<ExplorationFilterService>>());
|
||||
|
||||
var httpClientFactory = ServiceProvider.GetRequiredService<IHttpClientFactory>();
|
||||
var httpClient = httpClientFactory.CreateClient(Quick);
|
||||
httpClient.BaseAddress = new Uri(ContinuousImprovementEndpoint);
|
||||
ContinuousImprovementService = new ContinuousImprovementService(httpClient);
|
||||
|
||||
var featureFlagWrapper = Mock.Of<IFeatureFlagWrapper>();
|
||||
Mock.Get(featureFlagWrapper)
|
||||
.Setup(f => f.IsCiBenchmarkingEnabled(It.IsAny<bool>()))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
var strataSphereCompareService = Mock.Of<IStrataSphereCompareService>();
|
||||
|
||||
ExplorationService = new ExplorationService(
|
||||
JazzConnBuilderFactory,
|
||||
SnowflakeDatabaseContext,
|
||||
Mock.Of<IFiscalMonthResolver>(),
|
||||
featureFlagWrapper,
|
||||
strataSphereCompareService,
|
||||
ExplorationFilterService,
|
||||
Mock.Of<ILogger<ExplorationService>>());
|
||||
}
|
||||
|
||||
public static IEnumerable<TestCaseData> TestCaseSources()
|
||||
{
|
||||
foreach (bool value in new[] { false, true })
|
||||
{
|
||||
yield return new TestCaseData(nameof(ClientNetworkOpportunity), value)
|
||||
.SetName(nameof(ClientNetworkOpportunity) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(ClientNetworkOpportunityLink), value)
|
||||
.SetName(nameof(ClientNetworkOpportunityLink) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(ClientWarehouse), value)
|
||||
.SetName(nameof(ClientWarehouse) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(Comment), value)
|
||||
.SetName(nameof(Comment) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(DeviceIdentifierDimension), value)
|
||||
.SetName(nameof(DeviceIdentifierDimension) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(DeviceIdentifierTerm), value)
|
||||
.SetName(nameof(DeviceIdentifierTerm) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(GMDNCategory), value)
|
||||
.SetName(nameof(GMDNCategory) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(GMDNTerm), value)
|
||||
.SetName(nameof(GMDNTerm) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(GMDNTermCategory), value)
|
||||
.SetName(nameof(GMDNTermCategory) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(GUDIDDevice), value)
|
||||
.SetName(nameof(GUDIDDevice) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(GUDIDIdentifier), value)
|
||||
.SetName(nameof(GUDIDIdentifier) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(GUDIDTerm), value)
|
||||
.SetName(nameof(GUDIDTerm) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(NetworkOpportunity), value)
|
||||
.SetName(nameof(NetworkOpportunity) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(OpportunityTypeWorkbook), value)
|
||||
.SetName(nameof(OpportunityTypeWorkbook) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(OpportunityWorkbook), value)
|
||||
.SetName(nameof(OpportunityWorkbook) + $" {(value ? "First" : "Count")}");
|
||||
yield return new TestCaseData(nameof(TmpGUDIDIdentifier), value)
|
||||
.SetName(nameof(TmpGUDIDIdentifier) + $" {(value ? "First" : "Count")}");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task QueryTest<T, TI>(TI queryInfo)
|
||||
where TI : class
|
||||
where T : class
|
||||
{
|
||||
var query = Query(queryInfo);
|
||||
if (string.IsNullOrEmpty(query)) { return; }
|
||||
try
|
||||
{
|
||||
var result = await SnowflakeDatabaseContext
|
||||
.QueryAsync<T>(query, nameof(QueryTest), CancellationToken.None);
|
||||
await Verify(result, verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName(nameof(result)));
|
||||
}
|
||||
catch (SnowflakeDbException dbex)
|
||||
{
|
||||
// ILB
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string Query<T>(T queryInfo)
|
||||
where T : class
|
||||
=> "";
|
||||
|
||||
[OneTimeTearDown]
|
||||
public async Task TearDown()
|
||||
{
|
||||
try
|
||||
{
|
||||
await ((CentralDbContext)CentralDbContext).Database.EnsureDeletedAsync(CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Shared;
|
||||
using Strata.CoreLib.Claims;
|
||||
using Strata.FeatureFlags.Client;
|
||||
using Strata.Schema.Client;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security.Claims;
|
||||
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.JazzIntegrationTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
internal class TestCCOpportunityDetailService : IntegrationTestBase
|
||||
{
|
||||
protected ICCOpportunityDetailService _ccOpportunityDetailService;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void SetupChargeCodeDetailService()
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim("database_guid", GmAutomationDatabase.ToString())
|
||||
};
|
||||
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
|
||||
|
||||
var claimsPrincipalMock = Mock.Of<IClaimsPrincipalAccessor>();
|
||||
Mock.Get(claimsPrincipalMock)
|
||||
.Setup(x => x.GetCurrentClaimsPrincipal())
|
||||
.Returns(principal);
|
||||
|
||||
var featureFlagServiceMock = Mock.Of<IFeatureFlagServiceClient>();
|
||||
Mock.Get(featureFlagServiceMock)
|
||||
.Setup(x => x.IsEnabledAsync("is-decision-support-queries-enabled", It.IsAny<Guid>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
_ccOpportunityDetailService = new CCOpportunityDetailService(
|
||||
JazzConnBuilderFactory,
|
||||
Mock.Of<ISchemaServiceClient>(),
|
||||
SnowflakeDatabaseContext,
|
||||
featureFlagServiceMock,
|
||||
Mock.Of<ILogger<CCOpportunityDetailService>>(),
|
||||
claimsPrincipalMock);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(OpportunityCases))]
|
||||
public async Task OpportunityDetailQueryTest_Returns_Data(bool isBaseline, CCOpportunityDataModel opportunity)
|
||||
{
|
||||
// arrange
|
||||
var configuration = CreateConfiguration();
|
||||
|
||||
// act
|
||||
var results = await _ccOpportunityDetailService.GetCCOpportunityDetailQueryResult(
|
||||
opportunity,
|
||||
configuration,
|
||||
CancellationToken.None,
|
||||
isBaseline: true);
|
||||
|
||||
// assert
|
||||
results.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(OpportunityCases))]
|
||||
public async Task OpportunityDetailLevelCasesQueryTest_Returns_Data(bool isBaseline, CCOpportunityDataModel opportunity)
|
||||
{
|
||||
// arrange
|
||||
var configuration = CreateConfiguration();
|
||||
|
||||
// act
|
||||
var results = await _ccOpportunityDetailService.CCOpportunityLevelCasesQuery(
|
||||
opportunity,
|
||||
configuration,
|
||||
CancellationToken.None,
|
||||
isBaseline: true);
|
||||
|
||||
// assert
|
||||
results.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
private Configuration.Configuration CreateConfiguration()
|
||||
{
|
||||
return new Configuration.Configuration
|
||||
{
|
||||
OrgDefinedEndDate = new DateTime(2024, 1, 1)
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> OpportunityCases()
|
||||
{
|
||||
yield return new TestCaseData(true, GetSystemLevelData()).SetName("System");
|
||||
}
|
||||
|
||||
private static CCOpportunityDataModel GetSystemLevelData()
|
||||
{
|
||||
return new CCOpportunityDataModel
|
||||
{
|
||||
OpportunityLevel = OpportunityLevel.Organization.ToString(),
|
||||
ChargeCodeJson =
|
||||
@"{""HierarchyPaths"":[""CC|CCRU|Blood""],""Members"":[
|
||||
{""MemberGuid"":""4041e269-34c4-4a1d-bebf-c6e0a0799539"",""Name"":""10002765 - HB RBC LEUKOREDUCED EA UNIT"",""Id"":""3540""}]}",
|
||||
|
||||
ClinicalIndicatorJson =
|
||||
@"{""HierarchyPaths"":[""CLINICALINDICATOR|CLINICALINDI|188""],""Members"":[{""MemberGuid"":""d1e38321-5e37-4dad-9a54-ca8599763e7a"",""Name"":""All Readmissions"",""Id"":""188""}]}",
|
||||
|
||||
OpportunityLevelJson =
|
||||
@"{""HierarchyPaths"":[""SYS|SYSTEM""],
|
||||
""Members"":[{""MemberGuid"":""00000000-0000-0000-0000-000000000000"",
|
||||
""Id"":""1"",
|
||||
""Name"":""System""}]}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities;
|
||||
using Strata.Schema.Client;
|
||||
using Strata.Schema.Client.Dtos;
|
||||
using Strata.Schema.Client.Dtos.Info;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.JazzIntegrationTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
internal class TestGetENOpportunityDetailQuery : IntegrationTestBase
|
||||
{
|
||||
[Test]
|
||||
public async Task TestGetENOpportunityDetailQuery_Returns_Data()
|
||||
{
|
||||
// arrange
|
||||
var config = CreateConfiguration();
|
||||
var opportunity = CreateOpportunity();
|
||||
var schemaService = new Mock<ISchemaServiceClient>().Object;
|
||||
Mock.Get(schemaService)
|
||||
.Setup(x => x.GetDimensionMembersFromHierarchyPathsAsync(It.IsAny<Guid>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<MemberDto> { new() { Id = "188", } });
|
||||
Mock.Get(schemaService)
|
||||
.Setup(x => x.GetDimensionByGlobalIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ScoreDimensionInfoDto { DimensionGuid = new Guid("3465fcd8-9149-4d96-8456-3ecaa3ffdadd") });
|
||||
var logger = Mock.Of<ILogger<ENOpportunityDetailJob>>();
|
||||
|
||||
// act
|
||||
var results = await JazzDbContext.GetENOpportunityDetailQuery(opportunity, config, schemaService, logger, GmAutomationDatabase);
|
||||
|
||||
// assert
|
||||
results.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
private ENOpportunityDataModel CreateOpportunity()
|
||||
{
|
||||
return new ENOpportunityDataModel()
|
||||
{
|
||||
EncounterOpportunityJSON = @"{
|
||||
""EncounterGroup"":1,""IsSeasonal"":false, ""CaseTypes"":[],
|
||||
""PatientPopulations"":[{""Text"":""All Readmissions"",""Id"":""CLINICALINDICATOR|CLINICALINDI|188""}],
|
||||
""OpportunityLevel"":""Organization"",
|
||||
""EncounterOpportunityLevel"":0,
|
||||
""Entities"":[],""ServiceLines"":[],""Specialties"":[],""Providers"":[],""IsBaseline"":true}"
|
||||
};
|
||||
}
|
||||
|
||||
private Configuration.Configuration CreateConfiguration()
|
||||
{
|
||||
return new Configuration.Configuration
|
||||
{
|
||||
OrgDefinedEndDate = new DateTime(2024, 1, 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Strata.ContinuousImprovement.Biz.All.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Cube;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts.Jazz.Dimensions;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts.Reports;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts.Security;
|
||||
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Filter;
|
||||
using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.GeneralLedger.Details;
|
||||
using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.GMDN;
|
||||
using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns;
|
||||
using Strata.ContinuousImprovement.Biz.Initiatives;
|
||||
using Strata.ContinuousImprovement.Biz.Network;
|
||||
using Strata.ContinuousImprovement.Biz.Payroll.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.PeerGroups;
|
||||
using Strata.ContinuousImprovement.Biz.Population;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Events;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Metrics;
|
||||
using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.Score;
|
||||
using Strata.ContinuousImprovement.Biz.Security;
|
||||
using Strata.ContinuousImprovement.Biz.Shared;
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using Strata.ContinuousImprovement.Biz.UserProfiles;
|
||||
using Strata.ContinuousImprovement.Biz.UserSetting;
|
||||
using Strata.ContinuousImprovement.Biz.UtilizationVariation;
|
||||
using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities;
|
||||
using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes;
|
||||
using Strata.Stratasphere.Biz.DbContexts;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.JazzIntegrationTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public partial class TestJazzDbContext : IntegrationTestBase
|
||||
{
|
||||
|
||||
public async Task TestGeneric<T>(TestContext testContext) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
var count = await ((DbContext)JazzDbContext).Set<T>().CountAsync(CancellationToken.None);
|
||||
Assert.That(count, Is.GreaterThanOrEqualTo(0));
|
||||
if (count == 0) { return; }
|
||||
var result = await ((DbContext)JazzDbContext).Set<T>().FirstOrDefaultAsync(CancellationToken.None);
|
||||
Assert.That(result, Is.Not.Null.And.InstanceOf<T>());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ILB
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task JazzDbTestCases()
|
||||
{
|
||||
var types = ((DbContext)JazzDbContext).Model.GetEntityTypes()
|
||||
.Select(e => e.ClrType).OrderBy(c => c.Name).ToList();
|
||||
foreach (var type in types)
|
||||
{
|
||||
switch (type.Name)
|
||||
{
|
||||
case nameof(AllOpportunity):
|
||||
TestGeneric<AllOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(CaseTypeFamily):
|
||||
TestGeneric<CaseTypeFamily>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(CciUserSetting):
|
||||
TestGeneric<CciUserSetting>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(CCOpportunity):
|
||||
TestGeneric<CCOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(CCOpportunityDataModel):
|
||||
TestGeneric<CCOpportunityDataModel>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(CCOpportunityDetailQueryResult):
|
||||
TestGeneric<CCOpportunityDetailQueryResult>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(CCOpportunityLevelCaseQueryResult):
|
||||
TestGeneric<CCOpportunityLevelCaseQueryResult>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ChargeCode.ChargeCode):
|
||||
TestGeneric<ChargeCode.ChargeCode>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ChargeCodeOpportunityDetail):
|
||||
TestGeneric<ChargeCodeOpportunityDetail>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ChargeCodeOpportunityLevelCase):
|
||||
TestGeneric<ChargeCodeOpportunityLevelCase>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(Configuration.Configuration):
|
||||
TestGeneric<Configuration.Configuration>(TestContext.CurrentContext);
|
||||
break;␍
|
||||
case nameof(CostComponentRollup):
|
||||
TestGeneric<CostComponentRollup>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DboUserSetting):
|
||||
TestGeneric<DboUserSetting>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DimCaseType):
|
||||
TestGeneric<DimCaseType>(TestContext.CurrentContext);
|
||||
break;␍
|
||||
case nameof(DimCostComponent):
|
||||
TestGeneric<DimCostComponent>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DimDeviceIdentifier):
|
||||
TestGeneric<DimDeviceIdentifier>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DimSupply):
|
||||
TestGeneric<DimSupply>(TestContext.CurrentContext);
|
||||
break;␍ case nameof(DimEntity):
|
||||
TestGeneric<DimEntity>(TestContext.CurrentContext);
|
||||
break;␍ case nameof(StringValue):
|
||||
TestGeneric<StringValue>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ENOpportunity):
|
||||
TestGeneric<ENOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ENOpportunityDataModel):
|
||||
TestGeneric<ENOpportunityDataModel>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ENOpportunityMetricData):
|
||||
TestGeneric<ENOpportunityMetricData>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(EXOpportunity):
|
||||
TestGeneric<EXOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(FactGL):
|
||||
TestGeneric<FactGL>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(FactGLSampled):
|
||||
TestGeneric<FactGLSampled>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(FactStatistic):
|
||||
TestGeneric<FactStatistic>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(FactStatisticSampledMonthly):
|
||||
TestGeneric<FactStatisticSampledMonthly>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(FFFInitiativeTrackingData):
|
||||
TestGeneric<FFFInitiativeTrackingData>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(FFInitiativeTrackingData):
|
||||
TestGeneric<FFInitiativeTrackingData>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(FFOpportunity):
|
||||
TestGeneric<FFOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(FFOpportunityDataModel):
|
||||
TestGeneric<FFOpportunityDataModel>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(Folder):
|
||||
TestGeneric<Folder>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GLOpportunity):
|
||||
TestGeneric<GLOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(GLOpportunityDataModel):
|
||||
TestGeneric<GLOpportunityDataModel>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(Identity):
|
||||
TestGeneric<Identity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(Initiative):
|
||||
TestGeneric<Initiative>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(InitiativeComment):
|
||||
TestGeneric<InitiativeComment>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(InitiativeCriteria):
|
||||
TestGeneric<InitiativeCriteria>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(InitiativeCriteriaQueryResult):
|
||||
TestGeneric<InitiativeCriteriaQueryResult>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(InitiativeRollupColumn):
|
||||
TestGeneric<InitiativeRollupColumn>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(InitiativeRollupValue):
|
||||
TestGeneric<InitiativeRollupValue>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(InitiativeScore):
|
||||
TestGeneric<InitiativeScore>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(Lock):
|
||||
// Lock is inaccessible due to its protection level
|
||||
break;
|
||||
case nameof(NetworkOpportunityLink):
|
||||
TestGeneric<NetworkOpportunityLink>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(OpportunityMetricCohortData):
|
||||
TestGeneric<OpportunityMetricCohortData>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(OpportunityReport):
|
||||
TestGeneric<OpportunityReport>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(PeerGroup):
|
||||
TestGeneric<PeerGroup>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(PeerGroupCriteriaMapping):
|
||||
TestGeneric<PeerGroupCriteriaMapping>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(PROpportunity):
|
||||
TestGeneric<PROpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(PROpportunityDataModel):
|
||||
TestGeneric<PROpportunityDataModel>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(QVIBreakdown):
|
||||
TestGeneric<QVIBreakdown>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(QVIEvent):
|
||||
TestGeneric<QVIEvent>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(QVIOpportunity):
|
||||
TestGeneric<QVIOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(REReport):
|
||||
TestGeneric<REReport>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(RESQLReportRegisteredAttribute):
|
||||
TestGeneric<RESQLReportRegisteredAttribute>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(S3ACLSimple):
|
||||
TestGeneric<S3ACLSimple>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(S3SimplePermission):
|
||||
TestGeneric<S3SimplePermission>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(S3SimplePermissionDefault):
|
||||
TestGeneric<S3SimplePermissionDefault>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ScoreAttribute):
|
||||
TestGeneric<ScoreAttribute>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ScoreDimension):
|
||||
TestGeneric<ScoreDimension>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(SimplePermission):
|
||||
TestGeneric<SimplePermission>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(SourceOfSavingsReport):
|
||||
TestGeneric<SourceOfSavingsReport>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(SystemSetting):
|
||||
TestGeneric<SystemSetting>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(UserGroup):
|
||||
TestGeneric<UserGroup>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(UserProfile):
|
||||
TestGeneric<UserProfile>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(UVCaseType):
|
||||
TestGeneric<UVCaseType>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(UVOpportunity):
|
||||
TestGeneric<UVOpportunity>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(VariationOpportunityDataModel):
|
||||
TestGeneric<VariationOpportunityDataModel>(TestContext.CurrentContext);
|
||||
break;␍
|
||||
case nameof(ExplorationVariationOpportunityDataModel):
|
||||
TestGeneric<ExplorationVariationOpportunityDataModel>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(ViewScoreAttribute):
|
||||
TestGeneric<ViewScoreAttribute>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DimAprdrg):
|
||||
TestGeneric<DimAprdrg>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DimCpt):
|
||||
TestGeneric<DimCpt>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DimIcd10Dx):
|
||||
TestGeneric<DimIcd10Dx>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DimIcd10Px):
|
||||
TestGeneric<DimIcd10Px>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DimMsdrg):
|
||||
TestGeneric<DimMsdrg>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(DimMsdrgRollup):
|
||||
TestGeneric<DimMsdrgRollup>(TestContext.CurrentContext);
|
||||
break;
|
||||
case nameof(QviMetrics):
|
||||
TestGeneric<QviMetrics>(TestContext.CurrentContext);
|
||||
break;
|
||||
default:
|
||||
var pathArr = TestContext.CurrentContext.TestDirectory.Split(Path.DirectorySeparatorChar);
|
||||
var path = string.Join(Path.DirectorySeparatorChar, pathArr[..^3].Append("JazzIntegrationTests"));
|
||||
var fileName = Path.Combine(path, $"{nameof(TestJazzDbContext)}_{type.Name}.cs");
|
||||
if (File.Exists(fileName)) break;
|
||||
var testFile = @"
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.ContinuousImprovementServiceTests
|
||||
{
|
||||
[TestFixture, Category(""Integration""), Category(""CCI"")]
|
||||
public partial class TestJazzDbContext : IntegrationTestBase
|
||||
{
|
||||
[Test]
|
||||
public async Task Test" + type.Name + @"()
|
||||
{
|
||||
TestGeneric<" + type.Name + @">(TestContext.CurrentContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
";
|
||||
File.WriteAllText(fileName, testFile);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
await Verify(types, verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Strata.ApiLib.Core.Cors.Extensions;
|
||||
using Strata.ApiLib.Core.StrataAuthentication.Bootstrappers;
|
||||
using Strata.Configuration.Client.DependencyInjection.ConsoleApps;
|
||||
using Strata.ContinuousImprovement.Biz.Configurations;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public static class ServiceCollectionFactory
|
||||
{
|
||||
private static readonly Guid DevMaster = new("54409f55-5ddf-4748-8222-9ccfb04fb4f5");
|
||||
private static readonly Guid SeleniumAutomationDatabase = new("6bcd9090-3446-4cc1-b1aa-38afae76a1c6");
|
||||
private static readonly Guid SeleniumAutomationRefreshed4Master = new("feb7a099-8b2a-436c-9aba-0f853c8c60c9");
|
||||
private static readonly string IntegrationTesting = $"ci_{DateTime.Now:yyyyMMdd}_{Guid.NewGuid():N}";
|
||||
|
||||
public static IServiceCollection Create()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var jazzDbGuid = Debugger.IsAttached ? SeleniumAutomationRefreshed4Master : SeleniumAutomationDatabase;
|
||||
try
|
||||
{
|
||||
var configuration = StrataConfigurationFactory.GetConfiguration();
|
||||
services.AddSingleton(configuration);
|
||||
services.AddStrataAuthentication(configuration);
|
||||
services.AddStrataCors(configuration);
|
||||
services.AddContinuousImprovementServices(configuration, IntegrationTesting);
|
||||
services.AddAsyncDbContextFactory<JazzDbContext>(options =>
|
||||
{
|
||||
options
|
||||
.WithConnectionString((provider, token) => provider.GetConnectionStringFromSmc(jazzDbGuid, token))
|
||||
.WithDbContextOptions((connectionString, provider, builder) =>
|
||||
builder.UseSqlServer(connectionString, options => { options.CommandTimeout(300); }));
|
||||
});
|
||||
services.AddScoped<IStrategicOpportunityService, StrategicOpportunityService>();
|
||||
|
||||
services.AddLogging();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.FiscalMonths;
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.Schema.Client;
|
||||
using Strata.Schema.Client.Dtos;
|
||||
using Strata.Schema.Client.Dtos.Info;
|
||||
using Strata.SqlTools.Configuration.Common.AsyncFactory;
|
||||
using Strata.StrataSphereCompare.Client;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using VerifyNUnit;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Baseline regression tests for the exploration query refactor.
|
||||
///
|
||||
/// WORKFLOW:
|
||||
/// 1. On main (or a branch from main): run these tests to generate golden snapshots.
|
||||
/// Commit the resulting *.verified.txt files.
|
||||
/// 2. On the refactor branch: run the same tests.
|
||||
/// Verify diffs output against the golden snapshots — any divergence fails.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class FlexibleExplorationBaselineTest : IntegrationTestBase
|
||||
{
|
||||
private static readonly Guid _configurationGuid = Guid.Parse("aa4cba4f-2506-462c-8e34-c45b21653988");
|
||||
|
||||
private ExplorationDetailInfo _detailInfo;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public async Task SetupDetailInfo()
|
||||
{
|
||||
var summaryInfo = CreateSummaryInfo();
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(summaryInfo, CancellationToken.None);
|
||||
|
||||
if (!summaries.Any())
|
||||
throw new InvalidOperationException("No Case Type Family summaries returned — cannot run baseline tests.");
|
||||
|
||||
// Configure the schema service mock so the Gender ScoreMember filter resolves to
|
||||
// a real SQL fragment (fpes.GenderID IN (1)) rather than being silently skipped.
|
||||
// GenderID 1 = Male in the GM Automation test database.
|
||||
var genderDimensionGuid = Guid.NewGuid();
|
||||
Mock.Get(SchemaServiceClient)
|
||||
.Setup(s => s.GetDimensionByGlobalIdAsync("Gender", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new ScoreDimensionInfoDto { DimensionGuid = genderDimensionGuid });
|
||||
Mock.Get(SchemaServiceClient)
|
||||
.Setup(s => s.GetDimensionMembersFromHierarchyPathsAsync(genderDimensionGuid, It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new[] { new MemberDto { Id = "1" } });
|
||||
|
||||
// Rebuild ExplorationFilterService and ExplorationService with the configured mock
|
||||
// so the gender filter is active for all tests in this fixture.
|
||||
var filterService = new ExplorationFilterService(
|
||||
ServiceProvider.GetRequiredService<IAsyncDbContextFactory<JazzDbContext>>(),
|
||||
SchemaServiceClient,
|
||||
Mock.Of<ILogger<ExplorationFilterService>>());
|
||||
var featureFlagWrapper = Mock.Of<IFeatureFlagWrapper>();
|
||||
Mock.Get(featureFlagWrapper)
|
||||
.Setup(f => f.IsCiBenchmarkingEnabled(It.IsAny<bool>()))
|
||||
.ReturnsAsync(true);
|
||||
ExplorationService = new ExplorationService(
|
||||
JazzConnBuilderFactory,
|
||||
SnowflakeDatabaseContext,
|
||||
Mock.Of<IFiscalMonthResolver>(),
|
||||
featureFlagWrapper,
|
||||
Mock.Of<IStrataSphereCompareService>(),
|
||||
filterService,
|
||||
Mock.Of<ILogger<ExplorationService>>());
|
||||
|
||||
// Always pick the same CTF (highest encounter count) so snapshots are deterministic.
|
||||
var busiest = summaries.OrderByDescending(s => s.Data.TotalEncounters).First();
|
||||
|
||||
// Derive a real cost driver from the CTF's detail breakdown so the
|
||||
// encounter query (which filters on cc.costdriver = @CostDriver) returns data.
|
||||
var costDriverInfo = CreateSummaryInfo();
|
||||
costDriverInfo.CaseTypeFamilyId = busiest.Data.Id;
|
||||
var costDriverDetails = await ExplorationService.GetCaseTypeFamilySummaryDetailsAsync(costDriverInfo, CancellationToken.None);
|
||||
var costDriver = costDriverDetails
|
||||
.SelectMany(d => d.Children ?? [])
|
||||
.OrderByDescending(c => c.Data.TotalEncounters)
|
||||
.FirstOrDefault()?.Name
|
||||
?? throw new InvalidOperationException("No cost driver children found for the busiest CTF.");
|
||||
|
||||
_detailInfo = new ExplorationDetailInfo
|
||||
{
|
||||
ConfigurationGuid = _configurationGuid,
|
||||
CaseTypeFamilyId = busiest.Data.Id,
|
||||
CostDriver = costDriver,
|
||||
ExclusionCriteria = new ExclusionCriteria(),
|
||||
ExcludedEncounters = new List<long>(),
|
||||
IncludedEncounters = new List<long>(),
|
||||
Filters = new Biz.Exploration.Filters.Filter
|
||||
{
|
||||
DischargeDateStart = new DateTime(2024, 1, 1),
|
||||
DischargeDateEnd = new DateTime(2025, 1, 1),
|
||||
FilterChipItems = new List<FilterChipItem>
|
||||
{
|
||||
new FilterChipItem
|
||||
{
|
||||
ChipType = ChipType.DateRange,
|
||||
Key = ChipKey.DischargeDateID,
|
||||
Filters = [],
|
||||
DateRange = [new DateTime(2024, 1, 1), new DateTime(2025, 1, 1)]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ── No filters ──────────────────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilyDetailsAsync_MatchesBaseline()
|
||||
{
|
||||
var result = await ExplorationService.GetCaseTypeFamilyDetailsAsync(_detailInfo, CancellationToken.None);
|
||||
|
||||
await Verifier.Verify(result, verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetEncountersAsync_MatchesBaseline()
|
||||
{
|
||||
var result = await ExplorationService.GetEncountersAsync(_detailInfo, _configurationGuid, CancellationToken.None);
|
||||
|
||||
await Verifier.Verify(result.OrderBy(e => e.EncounterId), verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
// ── Boolean filter (charges > 0) ─────────────────────────────────────
|
||||
// IsGreaterThanZeroCharges hits the queryFilter path and reliably
|
||||
// produces a meaningful subset without risking empty results.
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilyDetailsAsync_WithChargesFilter_MatchesBaseline()
|
||||
{
|
||||
var info = CreateDetailInfo(extraFilter: CreateChargesFilter());
|
||||
|
||||
var result = await ExplorationService.GetCaseTypeFamilyDetailsAsync(info, CancellationToken.None);
|
||||
|
||||
await Verifier.Verify(result, verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetEncountersAsync_WithChargesFilter_MatchesBaseline()
|
||||
{
|
||||
var info = CreateDetailInfo(extraFilter: CreateChargesFilter());
|
||||
|
||||
var result = await ExplorationService.GetEncountersAsync(info, _configurationGuid, CancellationToken.None);
|
||||
|
||||
await Verifier.Verify(result.OrderBy(e => e.EncounterId), verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
// ── FPES ScoreMember filter (gender) ────────────────────────────────
|
||||
// Gender is an FPES-level filter — it resolves to fpes.GenderID IN (...)
|
||||
// via the schema service and lands in the encounterSummaryFilter placeholder.
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilyDetailsAsync_WithGenderFilter_MatchesBaseline()
|
||||
{
|
||||
var info = CreateDetailInfo(extraFilter: CreateGenderFilter());
|
||||
|
||||
var result = await ExplorationService.GetCaseTypeFamilyDetailsAsync(info, CancellationToken.None);
|
||||
|
||||
await Verifier.Verify(result, verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetEncountersAsync_WithGenderFilter_MatchesBaseline()
|
||||
{
|
||||
var info = CreateDetailInfo(extraFilter: CreateGenderFilter());
|
||||
|
||||
var result = await ExplorationService.GetEncountersAsync(info, _configurationGuid, CancellationToken.None);
|
||||
|
||||
await Verifier.Verify(result.OrderBy(e => e.EncounterId), verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
// ── IQR exclusions ───────────────────────────────────────────────────
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilyDetailsAsync_WithIqrExclusions_MatchesBaseline()
|
||||
{
|
||||
var info = CreateDetailInfo(exclusion: new ExclusionCriteria
|
||||
{
|
||||
CostExclusionType = ExclusionCriteriaType.Iqr,
|
||||
CostExclusionValue = 1.5,
|
||||
LosExclusionType = ExclusionCriteriaType.Iqr,
|
||||
LosExclusionValue = 1.5
|
||||
});
|
||||
|
||||
var result = await ExplorationService.GetCaseTypeFamilyDetailsAsync(info, CancellationToken.None);
|
||||
|
||||
await Verifier.Verify(result, verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetEncountersAsync_WithIqrExclusions_MatchesBaseline()
|
||||
{
|
||||
var info = CreateDetailInfo(exclusion: new ExclusionCriteria
|
||||
{
|
||||
CostExclusionType = ExclusionCriteriaType.Iqr,
|
||||
CostExclusionValue = 1.5,
|
||||
LosExclusionType = ExclusionCriteriaType.Iqr,
|
||||
LosExclusionValue = 1.5
|
||||
});
|
||||
|
||||
var result = await ExplorationService.GetEncountersAsync(info, _configurationGuid, CancellationToken.None);
|
||||
|
||||
await Verifier.Verify(result.OrderBy(e => e.EncounterId), verifySettings)
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
private ExplorationDetailInfo CreateDetailInfo(
|
||||
FilterChipItem extraFilter = null,
|
||||
ExclusionCriteria exclusion = null)
|
||||
{
|
||||
var dateFilter = new FilterChipItem
|
||||
{
|
||||
ChipType = ChipType.DateRange,
|
||||
Key = ChipKey.DischargeDateID,
|
||||
Filters = [],
|
||||
DateRange = [new DateTime(2024, 1, 1), new DateTime(2025, 1, 1)]
|
||||
};
|
||||
|
||||
var chips = extraFilter != null
|
||||
? new List<FilterChipItem> { dateFilter, extraFilter }
|
||||
: new List<FilterChipItem> { dateFilter };
|
||||
|
||||
return new ExplorationDetailInfo
|
||||
{
|
||||
ConfigurationGuid = _detailInfo.ConfigurationGuid,
|
||||
CaseTypeFamilyId = _detailInfo.CaseTypeFamilyId,
|
||||
CostDriver = _detailInfo.CostDriver,
|
||||
ExclusionCriteria = exclusion ?? new ExclusionCriteria(),
|
||||
ExcludedEncounters = new List<long>(),
|
||||
IncludedEncounters = new List<long>(),
|
||||
Filters = new Biz.Exploration.Filters.Filter
|
||||
{
|
||||
DischargeDateStart = new DateTime(2024, 1, 1),
|
||||
DischargeDateEnd = new DateTime(2025, 1, 1),
|
||||
FilterChipItems = chips
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Gender filter — FPES-level (encounterSummaryFilter placeholder).
|
||||
// GlobalId "Gender" maps to the Gender dimension in the schema service.
|
||||
// The Filters list contains the hierarchy path ID for "Male" as resolved
|
||||
// by the schema service; adjust the Id if needed for your test database.
|
||||
private static FilterChipItem CreateGenderFilter() => new()
|
||||
{
|
||||
ChipType = ChipType.ScoreMember,
|
||||
GlobalId = "Gender",
|
||||
Key = ChipKey.GenderID,
|
||||
Operator = OperatorType.In,
|
||||
Filters = new List<TreeNode> { new() { Id = "Male", DisplayText = "Male" } }
|
||||
};
|
||||
|
||||
private static FilterChipItem CreateChargesFilter() => new()
|
||||
{
|
||||
ChipType = ChipType.Boolean,
|
||||
Key = ChipKey.IsGreaterThanZeroCharges,
|
||||
IsSelected = true,
|
||||
Operator = OperatorType.In,
|
||||
Filters = new List<TreeNode>()
|
||||
};
|
||||
|
||||
private ExplorationCostDriverInfo CreateSummaryInfo() => new()
|
||||
{
|
||||
ConfigurationGuid = _configurationGuid,
|
||||
ExclusionCriteria = new ExclusionCriteria(),
|
||||
Filters = new Biz.Exploration.Filters.Filter
|
||||
{
|
||||
DischargeDateStart = new DateTime(2024, 1, 1),
|
||||
DischargeDateEnd = new DateTime(2025, 1, 1),
|
||||
FilterChipItems = new List<FilterChipItem>
|
||||
{
|
||||
new FilterChipItem
|
||||
{
|
||||
ChipType = ChipType.DateRange,
|
||||
Key = ChipKey.DischargeDateID,
|
||||
Filters = [],
|
||||
DateRange = [new DateTime(2024, 1, 1), new DateTime(2025, 1, 1)]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
using FluentAssertions;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class FlexibleExplorationSummaryDetailTest : IntegrationTestBase
|
||||
{
|
||||
private static readonly Guid _configurationGuid = Guid.Parse("aa4cba4f-2506-462c-8e34-c45b21653988");
|
||||
|
||||
[OneTimeSetUp]
|
||||
public async Task SetupExploration()
|
||||
{
|
||||
var explorationInfo = CreateExplorationInfo();
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
|
||||
if (!summaries.Any())
|
||||
throw new InvalidOperationException("No Case Type Family summaries returned for setup.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilySummaryDetails_WhenNoFilters_IsEquivalent()
|
||||
{
|
||||
// arrange
|
||||
var explorationInfo = CreateExplorationInfo();
|
||||
|
||||
// act
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
var testSummary = summaries.OrderByDescending(x => x.Data.TotalEncounters).First();
|
||||
explorationInfo.CaseTypeFamilyId = testSummary.Data.Id;
|
||||
|
||||
var testDetails = await ExplorationService.GetCaseTypeFamilySummaryDetailsAsync(explorationInfo, CancellationToken.None);
|
||||
var testDetail = testDetails.First();
|
||||
// assert
|
||||
testDetail.Data.Should().BeEquivalentTo(testSummary.Data,
|
||||
options => options
|
||||
.Using<double>(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.001))
|
||||
.WhenTypeIs<double>());
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ExclusionCriteriaTestCases))]
|
||||
public async Task GetCaseTypeFamilySummaries_WithExclusions(ExclusionCriteria exclusions)
|
||||
{
|
||||
await RunExclusionSummaryTests(exclusions);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilySummaryDetails_WithFilters_IsEquivalent()
|
||||
{
|
||||
// arrange - filtering on mortality (IsSelected = true => MortalityRate = 1)
|
||||
var booleanFilter = CreateBooleanFilter(ChipKey.Mortality);
|
||||
var explorationInfo = CreateExplorationInfo(booleanFilter);
|
||||
|
||||
// act
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
var testSummary = summaries.OrderByDescending(x => x.Data.TotalEncounters).First();
|
||||
explorationInfo.CaseTypeFamilyId = testSummary.Data.Id;
|
||||
|
||||
var testDetails = await ExplorationService.GetCaseTypeFamilySummaryDetailsAsync(explorationInfo, CancellationToken.None);
|
||||
var testDetail = testDetails.First();
|
||||
// assert
|
||||
testDetail.Data.Should().BeEquivalentTo(testSummary.Data,
|
||||
options => options
|
||||
.Using<double>(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.001))
|
||||
.WhenTypeIs<double>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilySummaryDetails_WithFiltersAndExclusions_IsEquivalent()
|
||||
{
|
||||
// arrange - iqr + mad exclusion combo with a boolean filter
|
||||
var booleanFilter = CreateBooleanFilter(ChipKey.Mortality);
|
||||
var exclusions = new ExclusionCriteria
|
||||
{
|
||||
CostExclusionType = ExclusionCriteriaType.Iqr,
|
||||
CostExclusionValue = 1.5,
|
||||
LosExclusionType = ExclusionCriteriaType.Mad,
|
||||
LosExclusionValue = 2
|
||||
};
|
||||
var explorationInfo = CreateExplorationInfo(booleanFilter, exclusions);
|
||||
|
||||
// act
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
var testSummary = summaries.OrderByDescending(x => x.Data.TotalEncounters).First();
|
||||
explorationInfo.CaseTypeFamilyId = testSummary.Data.Id;
|
||||
|
||||
var testDetails = await ExplorationService.GetCaseTypeFamilySummaryDetailsAsync(explorationInfo, CancellationToken.None);
|
||||
var testDetail = testDetails.First();
|
||||
// assert
|
||||
testDetail.Data.Should().BeEquivalentTo(testSummary.Data,
|
||||
options => options
|
||||
.Using<double>(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.001))
|
||||
.WhenTypeIs<double>());
|
||||
}
|
||||
|
||||
private FilterChipItem CreateDischargeDateFilter() => new()
|
||||
{
|
||||
ChipType = ChipType.DateRange,
|
||||
Filters = [],
|
||||
DateRange = [new DateTime(2024, 1, 1), new DateTime(2025, 1, 1)]
|
||||
};
|
||||
|
||||
private FilterChipItem CreateBooleanFilter(ChipKey key)
|
||||
{
|
||||
if (key is ChipKey.Mortality)
|
||||
{
|
||||
return new FilterChipItem
|
||||
{
|
||||
ChipType = ChipType.Boolean,
|
||||
GlobalId = "Mortality",
|
||||
IsSelected = true, // determines true/false of filter
|
||||
Key = ChipKey.Mortality,
|
||||
Operator = OperatorType.In,
|
||||
Filters = new List<TreeNode>()
|
||||
{
|
||||
new TreeNode
|
||||
{
|
||||
DisplayText = "Yes"
|
||||
}},
|
||||
};
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unsupported boolean filter: {key}", nameof(key));
|
||||
}
|
||||
|
||||
private ExplorationCostDriverInfo CreateExplorationInfo(FilterChipItem? additionalFilter = null, ExclusionCriteria? exclusion = null)
|
||||
{
|
||||
var dischargeDateFilter = CreateDischargeDateFilter();
|
||||
var filterChipItems = additionalFilter != null ?
|
||||
new List<FilterChipItem> { dischargeDateFilter, additionalFilter }
|
||||
: new List<FilterChipItem> { dischargeDateFilter };
|
||||
|
||||
return new ExplorationCostDriverInfo
|
||||
{
|
||||
ConfigurationGuid = _configurationGuid,
|
||||
ExclusionCriteria = exclusion ?? new ExclusionCriteria(),
|
||||
Filters = new Biz.Exploration.Filters.Filter
|
||||
{
|
||||
DischargeDateStart = dischargeDateFilter.DateRange.First(),
|
||||
DischargeDateEnd = dischargeDateFilter.DateRange.Last(),
|
||||
FilterChipItems = filterChipItems
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task RunExclusionSummaryTests(ExclusionCriteria exclusions)
|
||||
{
|
||||
// arrange
|
||||
var explorationInfo = CreateExplorationInfo(null, exclusions);
|
||||
|
||||
// act
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
var testSummary = summaries.OrderByDescending(x => x.Data.TotalEncounters).First();
|
||||
explorationInfo.CaseTypeFamilyId = testSummary.Data.Id;
|
||||
|
||||
var testDetails = await ExplorationService.GetCaseTypeFamilySummaryDetailsAsync(explorationInfo, CancellationToken.None);
|
||||
var testDetail = testDetails.First();
|
||||
// assert
|
||||
testDetail.Data.Should().BeEquivalentTo(testSummary.Data,
|
||||
options => options
|
||||
.Using<double>(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.001))
|
||||
.WhenTypeIs<double>());
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> ExclusionCriteriaTestCases()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new ExclusionCriteria
|
||||
{
|
||||
CostExclusionType = ExclusionCriteriaType.Mad,
|
||||
CostExclusionValue = 2,
|
||||
LosExclusionType = ExclusionCriteriaType.Mad,
|
||||
LosExclusionValue = 2
|
||||
}
|
||||
).SetName("GetCaseTypeFamilySummaryDetails_WithMADExclusions_IsEquivalent");
|
||||
|
||||
yield return new TestCaseData(
|
||||
new ExclusionCriteria
|
||||
{
|
||||
CostExclusionType = ExclusionCriteriaType.Iqr,
|
||||
CostExclusionValue = 1.5,
|
||||
LosExclusionType = ExclusionCriteriaType.Iqr,
|
||||
LosExclusionValue = 1.5
|
||||
}
|
||||
).SetName("GetCaseTypeFamilySummaryDetails_WithIQRExclusions_IsEquivalent");
|
||||
}
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
using FluentAssertions;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class FlexibleExplorationSummaryTest : IntegrationTestBase
|
||||
{
|
||||
private static readonly Guid _configurationGuid = Guid.Parse("aa4cba4f-2506-462c-8e34-c45b21653988");
|
||||
private static int _totalCaseTypeFamilyCount;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public async Task SetupExploration()
|
||||
{
|
||||
var explorationInfo = CreateExplorationInfo();
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
|
||||
if (!summaries.Any())
|
||||
throw new InvalidOperationException("No Case Type Family summaries returned for setup.");
|
||||
|
||||
_totalCaseTypeFamilyCount = summaries.Count();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilySummaries_WhenNoFilters_ReturnsAll()
|
||||
{
|
||||
// arrange
|
||||
var explorationInfo = CreateExplorationInfo();
|
||||
|
||||
// act
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
|
||||
// assert
|
||||
summaries.Should().HaveCount(_totalCaseTypeFamilyCount);
|
||||
await VerifySummaries(summaries);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ExclusionCriteriaTestCases))]
|
||||
public async Task GetCaseTypeFamilySummaries_WithExclusions(ExclusionCriteria exclusions)
|
||||
{
|
||||
await RunExclusionSummaryTests(exclusions);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilySummaries_WithFilters_ReturnsFiltered()
|
||||
{
|
||||
// arrange - filtering on mortality (IsSelected = true => MortalityRate = 1)
|
||||
var booleanFilter = CreateBooleanFilter(ChipKey.Mortality);
|
||||
var explorationInfo = CreateExplorationInfo(booleanFilter);
|
||||
|
||||
// act
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
|
||||
// assert
|
||||
summaries.Should().HaveCountGreaterThan(0);
|
||||
summaries.Should().HaveCountLessThan(_totalCaseTypeFamilyCount, "Filters should reduce data returned");
|
||||
// mortality rate only applies to included encounters
|
||||
summaries.Where(x => x.Data.IncludedEncounterCount > 0).Should().OnlyContain(s => s.Data.MortalityRate == 1);
|
||||
await VerifySummaries(summaries);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetCaseTypeFamilySummaries_WithFiltersAndExclusions_ReturnsFilteredAndExcluded()
|
||||
{
|
||||
// arrange - iqr + mad exclusion combo with a boolean filter
|
||||
var booleanFilter = CreateBooleanFilter(ChipKey.Mortality);
|
||||
var exclusions = new ExclusionCriteria
|
||||
{
|
||||
CostExclusionType = ExclusionCriteriaType.Iqr,
|
||||
CostExclusionValue = 1.5,
|
||||
LosExclusionType = ExclusionCriteriaType.Mad,
|
||||
LosExclusionValue = 2
|
||||
};
|
||||
var explorationInfo = CreateExplorationInfo(booleanFilter, exclusions);
|
||||
|
||||
// act
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
|
||||
// assert
|
||||
summaries.Should().HaveCountGreaterThan(0);
|
||||
summaries.Should().HaveCountLessThan(_totalCaseTypeFamilyCount);
|
||||
// mortality rate only applies to included encounters
|
||||
summaries.Where(x => x.Data.IncludedEncounterCount > 0).Should().OnlyContain(s => s.Data.MortalityRate == 1);
|
||||
summaries.Should().Contain(s => s.Data.ExcludedEncountersAboveMedian > 0);
|
||||
await VerifySummaries(summaries);
|
||||
}
|
||||
|
||||
private FilterChipItem CreateDischargeDateFilter() => new()
|
||||
{
|
||||
ChipType = ChipType.DateRange,
|
||||
Filters = [],
|
||||
DateRange = [new DateTime(2024, 1, 1), new DateTime(2025, 1, 1)]
|
||||
};
|
||||
|
||||
private FilterChipItem CreateBooleanFilter(ChipKey key)
|
||||
{
|
||||
if (key is ChipKey.Mortality)
|
||||
{
|
||||
return new FilterChipItem
|
||||
{
|
||||
ChipType = ChipType.Boolean,
|
||||
GlobalId = "Mortality",
|
||||
IsSelected = true, // determines true/false of filter
|
||||
Key = ChipKey.Mortality,
|
||||
Operator = OperatorType.In,
|
||||
Filters = new List<TreeNode>()
|
||||
{
|
||||
new TreeNode
|
||||
{
|
||||
DisplayText = "Yes"
|
||||
}},
|
||||
};
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unsupported boolean filter: {key}", nameof(key));
|
||||
}
|
||||
|
||||
private ExplorationInfo CreateExplorationInfo(FilterChipItem? additionalFilter = null, ExclusionCriteria? exclusion = null)
|
||||
{
|
||||
var dischargeDateFilter = CreateDischargeDateFilter();
|
||||
var filterChipItems = additionalFilter != null ?
|
||||
new List<FilterChipItem> { dischargeDateFilter, additionalFilter }
|
||||
: new List<FilterChipItem> { dischargeDateFilter };
|
||||
|
||||
return new ExplorationInfo
|
||||
{
|
||||
ConfigurationGuid = _configurationGuid,
|
||||
ExclusionCriteria = exclusion ?? new ExclusionCriteria(),
|
||||
Filters = new Biz.Exploration.Filters.Filter
|
||||
{
|
||||
DischargeDateStart = dischargeDateFilter.DateRange.First(),
|
||||
DischargeDateEnd = dischargeDateFilter.DateRange.Last(),
|
||||
FilterChipItems = filterChipItems
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task AssertExclusionsWereApplied(IEnumerable<CaseTypeFamilySummaryData> summaries)
|
||||
{
|
||||
summaries.Should().HaveCount(_totalCaseTypeFamilyCount);
|
||||
summaries.Should().Contain(s => s.Data.ExcludedEncountersAboveMedian > 0);
|
||||
}
|
||||
|
||||
private Task VerifySummaries(IEnumerable<CaseTypeFamilySummaryData> summaries)
|
||||
{
|
||||
summaries.Should().NotBeNull().And.NotBeEmpty();
|
||||
summaries.Should().OnlyContain(s => !string.IsNullOrWhiteSpace(s.Key));
|
||||
summaries.Should().OnlyContain(s => !string.IsNullOrWhiteSpace(s.Name));
|
||||
summaries.Should().AllSatisfy(s => s.Key.Should().MatchRegex(@"^\d+-\d+$", "Key should follow format 'level-id'"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.Should().NotBeNull("Each summary should have data"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.Key.Should().MatchRegex(@"^\d+-\d+$", "Key should follow format 'level-id'"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.Id.Should().BeGreaterThan(0, "Each summary data should have a valid ID"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.TotalEncounters.Should().BeGreaterThanOrEqualTo(0, "Each summary data should have a valid TotalEncounters"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.TotalVariationEncounters.Should().BeGreaterThanOrEqualTo(0, "Each summary data should have a valid TotalVariationEncounters"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.IncludedEncounterCount.Should().BeGreaterThanOrEqualTo(0, "Each summary data should have a valid IncludedEncounterCount"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.IdentifiedVariation.Should().BeGreaterThanOrEqualTo(0, "Each summary data should have a valid IdentifiedVariation"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.MortalityRate.Should().BeInRange(0, 1, "MortalityRate should be between 0 and 1"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.MedianLos.Should().BeGreaterOrEqualTo(0, "MedianLos should be non-negative"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.MedianCost.Should().BeGreaterOrEqualTo(0, "MedianCost should be non-negative"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.IncludedEncountersBelowMedian.Should().BeGreaterOrEqualTo(0, "IncludedEncountersBelowMedian should be non-negative"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.IncludedEncountersAboveMedian.Should().BeGreaterOrEqualTo(0, "IncludedEncountersAboveMedian should be non-negative"));
|
||||
summaries.Should().AllSatisfy(s => s.Data.IncludedVariation.Should().BeGreaterOrEqualTo(0, "IncludedVariation should be non-negative"));
|
||||
return Verify(summaries, verifySettings)
|
||||
.UseDirectory("Exploration")
|
||||
.UseFileName(TestContext.CurrentContext.TestName());
|
||||
}
|
||||
|
||||
private async Task RunExclusionSummaryTests(ExclusionCriteria exclusions)
|
||||
{
|
||||
// act
|
||||
var explorationInfo = CreateExplorationInfo(null, exclusions);
|
||||
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
|
||||
|
||||
// assert
|
||||
await AssertExclusionsWereApplied(summaries);
|
||||
await VerifySummaries(summaries);
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> ExclusionCriteriaTestCases()
|
||||
{
|
||||
yield return new TestCaseData(
|
||||
new ExclusionCriteria
|
||||
{
|
||||
CostExclusionType = ExclusionCriteriaType.Mad,
|
||||
CostExclusionValue = 2,
|
||||
LosExclusionType = ExclusionCriteriaType.Mad,
|
||||
LosExclusionValue = 2
|
||||
}
|
||||
).SetName("GetCaseTypeFamilySummaries_WithMADExclusions_ReturnsWithExclusions");
|
||||
|
||||
yield return new TestCaseData(
|
||||
new ExclusionCriteria
|
||||
{
|
||||
CostExclusionType = ExclusionCriteriaType.Iqr,
|
||||
CostExclusionValue = 1.5,
|
||||
LosExclusionType = ExclusionCriteriaType.Iqr,
|
||||
LosExclusionValue = 1.5
|
||||
}
|
||||
).SetName("GetCaseTypeFamilySummaries_WithIQRExclusions_ReturnsWithExclusions");
|
||||
}
|
||||
}
|
||||
}
|
||||
+457
@@ -0,0 +1,457 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moq;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
|
||||
using Strata.ContinuousImprovement.Biz.Notification;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries;
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using Strata.DataSchema.Client;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using Strata.DataSchema.Models.Schema;
|
||||
using Strata.Schema.Client;
|
||||
using Strata.Schema.Client.Dtos;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static Strata.ContinuousImprovement.Biz.Exploration.ExplorationService;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class StrategicChargeCodeOpportunityDetailServiceTests : IntegrationTestBase
|
||||
{
|
||||
private IStrategicChargeCodeOpportunityDetailService _strategicOpportunityDetailService;
|
||||
private Mock<IDataSchemaService> _mockDataSchemaService = new Mock<IDataSchemaService>();
|
||||
private Mock<ISchemaServiceClient> _mockSchemaServiceClient = new Mock<ISchemaServiceClient>();
|
||||
private Mock<IExplorationFilterService> _mockExplorationService = new Mock<IExplorationFilterService>();
|
||||
private Mock<IStrategicItemDimensionSyncService> _mockStrategicItemDimensionSyncService = new Mock<IStrategicItemDimensionSyncService>();
|
||||
private IStrategicOpportunityDetailQueryBuilder _strategicOpportunityDetailQueryBuilder;
|
||||
|
||||
private const long TestOpportunityId = 999_000_001;
|
||||
private const long TestMeasureId = TestOpportunityId * 10 + 2;
|
||||
private const string MeasureFiltersJSON = "[17993, 17995, 18001]";
|
||||
private static readonly Guid ChargeCodeDimensionGuid = new("aaaaaaaa-0000-0000-0000-000000000001");
|
||||
private static readonly Guid ClinicalIndicatorDimensionGuid = new("bbbbbbbb-0000-0000-0000-000000000002");
|
||||
private const long TestExplorationPopulationId = 999_000_002;
|
||||
private const long TestOpportunityId_Exploration = 999_000_002;
|
||||
private const long TestMeasureId_Exploration = TestOpportunityId_Exploration * 10 + 2;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var notificationHubClient = new Mock<IHubContext<NotificationHub, INotificationHub>>();
|
||||
var mockNotificationClients = new Mock<IHubClients<INotificationHub>>();
|
||||
var mockNotificationGroup = new Mock<INotificationHub>();
|
||||
var distributionProcessFactory = new Mock<IDistributionProcessFactory>();
|
||||
|
||||
notificationHubClient.Setup(h => h.Clients).Returns(mockNotificationClients.Object);
|
||||
mockNotificationClients.Setup(c => c.Group(It.IsAny<string>())).Returns(mockNotificationGroup.Object);
|
||||
mockNotificationGroup
|
||||
.Setup(g => g.SendStrategicInitiativeTrackerSuccess(It.IsAny<string>(), It.IsAny<byte>(), It.IsAny<DateTime?>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
mockNotificationGroup
|
||||
.Setup(g => g.SendStrategicInitiativeTrackerError(It.IsAny<string>(), It.IsAny<byte>(), It.IsAny<DateTime?>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
_mockDataSchemaService.Setup(x =>
|
||||
x.GetDataTableBySqlFullNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((string tableName, CancellationToken _) => new DataTable
|
||||
{
|
||||
SqlAlias = tableName.Split('.').Last()
|
||||
});
|
||||
|
||||
_mockDataSchemaService.Setup(x =>
|
||||
x.GetDataColumnsBySqlColumnNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync([]);
|
||||
|
||||
_mockSchemaServiceClient
|
||||
.Setup(x => x.GetDimensionByGlobalIdAsync("Charge Code", It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new Schema.Client.Dtos.Info.ScoreDimensionInfoDto { DimensionGuid = ChargeCodeDimensionGuid });
|
||||
|
||||
_mockSchemaServiceClient
|
||||
.Setup(x => x.GetDimensionByGlobalIdAsync(It.Is<string>(s => s != "Charge Code"), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new Schema.Client.Dtos.Info.ScoreDimensionInfoDto { DimensionGuid = ClinicalIndicatorDimensionGuid });
|
||||
|
||||
_mockSchemaServiceClient
|
||||
.Setup(x => x.GetDimensionMembersFromHierarchyPathsAsync(
|
||||
ChargeCodeDimensionGuid,
|
||||
It.IsAny<IEnumerable<string>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync([
|
||||
new MemberDto { Id = "17993", Name = "39017762 - LAB CAB FEE(FROM BBACCA)*ARC" },
|
||||
new MemberDto { Id = "17995", Name = "39022140 - LAB FROZEN POOLED CRYO PRODUCT(5)*ARC" },
|
||||
new MemberDto { Id = "18001", Name = "39022185 - LAB LEUKOFILTERED CELLS 1 UNIT *ARC" },
|
||||
]);
|
||||
|
||||
_mockSchemaServiceClient
|
||||
.Setup(x => x.GetDimensionMembersFromHierarchyPathsAsync(
|
||||
ClinicalIndicatorDimensionGuid,
|
||||
It.IsAny<IEnumerable<string>>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync([new MemberDto { Id = "188", Name = "All Readmissions" }]);
|
||||
|
||||
_mockSchemaServiceClient.Setup(x =>
|
||||
x.GetHierarchyNodesFromHierarchyPathsAsync(It.IsAny<Guid>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync([]);
|
||||
|
||||
_strategicOpportunityDetailQueryBuilder = new StrategicOpportunityDetailQueryBuilder();
|
||||
|
||||
_strategicOpportunityDetailService = new StrategicChargeCodeOpportunityDetailService(
|
||||
JazzConnBuilderFactory,
|
||||
(CentralDbContext)CentralDbContext,
|
||||
TestUtilities.GetClaimsPrincipalAccessor(),
|
||||
_mockDataSchemaService.Object,
|
||||
_mockSchemaServiceClient.Object,
|
||||
SnowflakeDatabaseContext,
|
||||
_mockExplorationService.Object,
|
||||
distributionProcessFactory.Object,
|
||||
_strategicOpportunityDetailQueryBuilder,
|
||||
notificationHubClient.Object,
|
||||
_mockStrategicItemDimensionSyncService.Object,
|
||||
(Microsoft.Extensions.Logging.ILogger<StrategicChargeCodeOpportunityDetailService>)logger);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public async Task CleanupData()
|
||||
{
|
||||
await SnowflakeDatabaseContext.ExecuteCommandAsync(
|
||||
$"DELETE FROM cci.FactStrategicChargeCodeDetail WHERE OpportunityId IN ({TestOpportunityId}, {TestOpportunityId_Exploration})");
|
||||
|
||||
await SnowflakeDatabaseContext.ExecuteCommandAsync(
|
||||
$"DELETE FROM cci.StrategicOpportunityMeasureDetail WHERE MeasureId IN " +
|
||||
$"(SELECT MeasureId FROM cci.StrategicOpportunityMeasureDetail WHERE MeasureId >= {TestOpportunityId * 10})");
|
||||
|
||||
var centralDb = (CentralDbContext)CentralDbContext;
|
||||
var measures = centralDb.StrategicOpportunityMeasures
|
||||
.Where(m => m.OpportunityId == TestOpportunityId || m.OpportunityId == TestOpportunityId_Exploration);
|
||||
centralDb.StrategicOpportunityMeasures.RemoveRange(measures);
|
||||
|
||||
var opportunity = await centralDb.StrategicOpportunityChargeCodes
|
||||
.FirstOrDefaultAsync(o => o.OpportunityId == TestOpportunityId || o.OpportunityId == TestOpportunityId_Exploration);
|
||||
if (opportunity != null)
|
||||
centralDb.StrategicOpportunityChargeCodes.Remove(opportunity);
|
||||
|
||||
var explorationPopulation = await centralDb.ExplorationPopulations
|
||||
.FirstOrDefaultAsync(p => p.ExplorationPopulationId == TestExplorationPopulationId);
|
||||
if (explorationPopulation != null)
|
||||
centralDb.ExplorationPopulations.Remove(explorationPopulation);
|
||||
|
||||
await centralDb.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// Tests for standard Tracking opportunity (patient population)
|
||||
[Test, Ignore("Test database needs DS Encounter Cost datasource")]
|
||||
public async Task PopulateTrackingDetails_WithMeasures_PopulatesMeasureDetailRows()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTrackingOpportunity(patientPopulationHPath: "CLINICALINDICATOR|CLINICALINDI|188");
|
||||
opportunity.Measures =
|
||||
[
|
||||
new StrategicOpportunityMeasure
|
||||
{
|
||||
MeasureId = TestMeasureId,
|
||||
OpportunityId = TestOpportunityId,
|
||||
MeasureType = MeasureType.DirectVariableCost,
|
||||
MeasureGroup = MeasureGroup.Primary,
|
||||
FiltersJSON = MeasureFiltersJSON,
|
||||
StrataId = 1
|
||||
}
|
||||
];
|
||||
await SeedOpportunityInCentralDb(opportunity);
|
||||
|
||||
SetupBuildSqlQueryForTracking();
|
||||
|
||||
// Act
|
||||
await _strategicOpportunityDetailService.PopulateTrackingDetails(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert - measure detail rows written
|
||||
var measureRows = await SnowflakeDatabaseContext.QueryAsync<StrategicOpportunityMeasureDetail>(
|
||||
$"SELECT * FROM cci.StrategicOpportunityMeasureDetail WHERE MeasureId = {opportunity.Measures.First().MeasureId}",
|
||||
nameof(PopulateTrackingDetails_WithMeasures_PopulatesMeasureDetailRows));
|
||||
// Assert - verify rows were written to Snowflake
|
||||
var rows = await SnowflakeDatabaseContext.QueryAsync<StrategicOpportunityDetail>(
|
||||
$"SELECT * FROM cci.FactStrategicChargeCodeDetail WHERE OpportunityId = {TestOpportunityId} AND IsBaseline = FALSE",
|
||||
nameof(PopulateTrackingDetails_WithMeasures_PopulatesMeasureDetailRows));
|
||||
|
||||
measureRows.Should().NotBeNullOrEmpty();
|
||||
rows.Should().NotBeNullOrEmpty();
|
||||
|
||||
// Assert - AverageValue is written back to CentralDb
|
||||
var savedMeasure = await ((CentralDbContext)CentralDbContext)
|
||||
.StrategicOpportunityMeasures
|
||||
.FirstOrDefaultAsync(m => m.MeasureId == opportunity.Measures.First().MeasureId);
|
||||
|
||||
savedMeasure.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test, Ignore("Test database needs DS Encounter Cost datasource")]
|
||||
public async Task PopulateTrackingDetails_WhenStatusIsNotTracking_SkipsDetailPopulationAndSucceeds()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTrackingOpportunity(patientPopulationHPath: "CLINICALINDICATOR|CLINICALINDI|188");
|
||||
opportunity.Status = (byte)StrategicOpportunityStatus.CompleteMonitoring;
|
||||
opportunity.Measures =
|
||||
[
|
||||
new StrategicOpportunityMeasure
|
||||
{
|
||||
MeasureId = TestMeasureId,
|
||||
OpportunityId = TestOpportunityId,
|
||||
MeasureType = MeasureType.DirectVariableCost,
|
||||
MeasureGroup = MeasureGroup.Primary,
|
||||
FiltersJSON = MeasureFiltersJSON,
|
||||
StrataId = 1
|
||||
}
|
||||
];
|
||||
await SeedOpportunityInCentralDb(opportunity);
|
||||
|
||||
SetupBuildSqlQueryForMonitoring();
|
||||
|
||||
// Act
|
||||
await _strategicOpportunityDetailService.PopulateTrackingDetails(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert - no detail rows written for non-Tracking status
|
||||
var rows = await SnowflakeDatabaseContext.QueryAsync<StrategicOpportunityDetail>(
|
||||
$"SELECT * FROM cci.FactStrategicChargeCodeDetail WHERE OpportunityId = {TestOpportunityId} AND IsBaseline = FALSE",
|
||||
nameof(PopulateTrackingDetails_WhenStatusIsNotTracking_SkipsDetailPopulationAndSucceeds));
|
||||
|
||||
rows.Should().BeEmpty("detail rows should not be populated when status is not Tracking");
|
||||
|
||||
// Assert - tracking is still updated
|
||||
var savedOpportunity = await ((CentralDbContext)CentralDbContext)
|
||||
.StrategicOpportunityChargeCodes
|
||||
.FirstOrDefaultAsync(o => o.OpportunityId == TestOpportunityId);
|
||||
|
||||
savedOpportunity.LastRunTrackingStatus.Should().Be(RunTrackingStatus.Success);
|
||||
}
|
||||
|
||||
[Test, Ignore("Test database needs DS Encounter Cost datasource")]
|
||||
public async Task PopulateTrackingDetails_WithMeasures_PopulatesMeasureDetailRows_Exploration()
|
||||
{
|
||||
// Arrange
|
||||
await SeedExplorationPopulationInCentralDb();
|
||||
var opportunity = CreateExplorationOpportunity();
|
||||
opportunity.Measures =
|
||||
[
|
||||
new StrategicOpportunityMeasure
|
||||
{
|
||||
MeasureId = TestMeasureId_Exploration,
|
||||
OpportunityId = opportunity.OpportunityId,
|
||||
MeasureType = MeasureType.DirectVariableCost,
|
||||
MeasureGroup = MeasureGroup.Primary,
|
||||
FiltersJSON = MeasureFiltersJSON,
|
||||
StrataId = 1
|
||||
}
|
||||
];
|
||||
await SeedOpportunityInCentralDb(opportunity);
|
||||
_mockExplorationService
|
||||
.Setup(x => x.GetFilterStringsAsync(
|
||||
It.IsAny<IEnumerable<FilterChipItem>>(),
|
||||
It.IsAny<IQueryParamBase>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Callback<IEnumerable<FilterChipItem>, IQueryParamBase, CancellationToken>(
|
||||
(_, queryParams, _) =>
|
||||
{
|
||||
queryParams.DischargeDateStart = "2024-01-01";
|
||||
queryParams.DischargeDateEnd = "2024-12-31";
|
||||
})
|
||||
.ReturnsAsync((
|
||||
string.Empty,
|
||||
string.Empty));
|
||||
SetupBuildSqlQueryForTrackingExploration();
|
||||
|
||||
// Act
|
||||
await _strategicOpportunityDetailService.PopulateTrackingDetails(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert - measure detail rows written
|
||||
var measureRows = await SnowflakeDatabaseContext.QueryAsync<StrategicOpportunityMeasureDetail>(
|
||||
$"SELECT * FROM cci.StrategicOpportunityMeasureDetail WHERE MeasureId = {opportunity.Measures.First().MeasureId}",
|
||||
nameof(PopulateTrackingDetails_WithMeasures_PopulatesMeasureDetailRows));
|
||||
// Assert - verify rows were written to Snowflake
|
||||
var rows = await SnowflakeDatabaseContext.QueryAsync<StrategicOpportunityDetail>(
|
||||
$"SELECT * FROM cci.FactStrategicChargeCodeDetail WHERE OpportunityId = {opportunity.OpportunityId} AND IsBaseline = FALSE",
|
||||
nameof(PopulateTrackingDetails_WithMeasures_PopulatesMeasureDetailRows));
|
||||
|
||||
measureRows.Should().NotBeNullOrEmpty();
|
||||
rows.Should().NotBeNullOrEmpty();
|
||||
|
||||
// Assert - AverageValue is written back to CentralDb
|
||||
var savedMeasure = await ((CentralDbContext)CentralDbContext)
|
||||
.StrategicOpportunityMeasures
|
||||
.FirstOrDefaultAsync(m => m.MeasureId == opportunity.Measures.First().MeasureId);
|
||||
|
||||
savedMeasure.Should().NotBeNull();
|
||||
}
|
||||
|
||||
private StrategicOpportunityChargeCode CreateTrackingOpportunity(
|
||||
string patientPopulationHPath,
|
||||
DateOnly? trackingStart = null,
|
||||
DateOnly? trackingEnd = null) =>
|
||||
new()
|
||||
{
|
||||
OpportunityId = TestOpportunityId,
|
||||
PatientPopulationHPath = patientPopulationHPath,
|
||||
ExplorationPopulationId = 0,
|
||||
ChargeCodeHPath = "CC|CCRU|Blood",
|
||||
OpportunityFiltersJSON = "[]",
|
||||
Status = (byte)StrategicOpportunityStatus.Tracking,
|
||||
TrackingStartDate = trackingStart ?? new DateOnly(2024, 1, 1),
|
||||
TrackingEndDate = trackingEnd ?? new DateOnly(2024, 12, 31),
|
||||
BaselineStartDate = new DateOnly(2023, 1, 1),
|
||||
BaselineEndDate = new DateOnly(2023, 12, 31),
|
||||
LastRunTrackingDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
EstimatedTrackingDuration = 12,
|
||||
StrataId = 1,
|
||||
Measures = []
|
||||
};
|
||||
|
||||
private StrategicOpportunityChargeCode CreateExplorationOpportunity(
|
||||
DateOnly? trackingStart = null,
|
||||
DateOnly? trackingEnd = null) =>
|
||||
new()
|
||||
{
|
||||
OpportunityId = TestOpportunityId_Exploration,
|
||||
PatientPopulationHPath = null,
|
||||
ExplorationPopulationId = TestExplorationPopulationId,
|
||||
ChargeCodeHPath = "CC|CCRU|Blood",
|
||||
OpportunityFiltersJSON = "[]",
|
||||
Status = (byte)StrategicOpportunityStatus.Tracking,
|
||||
TrackingStartDate = trackingStart ?? new DateOnly(2024, 1, 1),
|
||||
TrackingEndDate = trackingEnd ?? new DateOnly(2024, 12, 31),
|
||||
BaselineStartDate = new DateOnly(2023, 1, 1),
|
||||
BaselineEndDate = new DateOnly(2023, 12, 31),
|
||||
LastRunTrackingDate = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
EstimatedTrackingDuration = 12,
|
||||
StrataId = 1,
|
||||
Measures = []
|
||||
};
|
||||
|
||||
private async Task SeedOpportunityInCentralDb(StrategicOpportunityChargeCode opportunity)
|
||||
{
|
||||
var centralDb = (CentralDbContext)CentralDbContext;
|
||||
|
||||
// Detach any already-tracked instance with the same key to avoid tracking conflicts
|
||||
var tracked = centralDb.ChangeTracker.Entries<StrategicOpportunityChargeCode>()
|
||||
.FirstOrDefault(e => e.Entity.OpportunityId == opportunity.OpportunityId);
|
||||
if (tracked != null)
|
||||
tracked.State = Microsoft.EntityFrameworkCore.EntityState.Detached;
|
||||
|
||||
var existing = await centralDb.StrategicOpportunityChargeCodes
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(o => o.OpportunityId == opportunity.OpportunityId);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
centralDb.StrategicOpportunityChargeCodes.Remove(existing);
|
||||
await centralDb.SaveChangesAsync();
|
||||
}
|
||||
|
||||
centralDb.StrategicOpportunityChargeCodes.Add(opportunity);
|
||||
await centralDb.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task SeedExplorationPopulationInCentralDb()
|
||||
{
|
||||
var centralDb = (CentralDbContext)CentralDbContext;
|
||||
|
||||
var existing = await centralDb.ExplorationPopulations
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(p => p.ExplorationPopulationId == TestExplorationPopulationId);
|
||||
|
||||
if (existing != null) return;
|
||||
|
||||
centralDb.ExplorationPopulations.Add(new Population.ExplorationPopulation
|
||||
{
|
||||
ExplorationPopulationId = TestExplorationPopulationId,
|
||||
Name = "Test Exploration Population",
|
||||
CaseTypeFamilyId = 8016,
|
||||
FiltersJSON = $@"[{{
|
||||
""key"": 25,
|
||||
""chipType"": {(byte)ChipType.DateRange},
|
||||
""operator"": 0,
|
||||
""dateRange"": [""{new DateTime(2024, 1, 1):yyyy-MM-dd}"", ""{new DateTime(2024, 12, 31):yyyy-MM-dd}""],
|
||||
""filters"": [],
|
||||
""isSelected"": null
|
||||
}}]",
|
||||
StrataId = 1
|
||||
});
|
||||
await centralDb.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private void SetupBuildSqlQueryForTracking()
|
||||
{
|
||||
_mockDataSchemaService.SetupSequence(x =>
|
||||
x.BuildSqlQuery(It.IsAny<QueryConfig>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new SqlResponse
|
||||
{
|
||||
SqlQuery = StrategicOpportunityQueryConstants.TrackingSelectSql,
|
||||
Parameters =
|
||||
[
|
||||
new(":dischargedatetime_Start_0", new DateTime(2024, 1, 1)),
|
||||
new(":dischargedatetime_End_1", new DateTime(2024, 12, 31)),
|
||||
new(":clinicalindicatorid_2", 188),
|
||||
new(":dischargedatetime_Start_3", new DateTime(2024, 1, 1)),
|
||||
new(":dischargedatetime_End_4", new DateTime(2024, 12, 31)),
|
||||
new(":clinicalindicatorid_5", 188),
|
||||
]
|
||||
})
|
||||
.ReturnsAsync(new SqlResponse
|
||||
{
|
||||
SqlQuery = StrategicOpportunityQueryConstants.MeasureSelectSql,
|
||||
Parameters =
|
||||
[
|
||||
new(":dischargedatetime_Start_0", new DateTime(2024, 1, 1)),
|
||||
new(":dischargedatetime_End_1", new DateTime(2024, 12, 31)),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupBuildSqlQueryForTrackingExploration()
|
||||
{
|
||||
_mockDataSchemaService.SetupSequence(x =>
|
||||
x.BuildSqlQuery(It.IsAny<QueryConfig>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new SqlResponse
|
||||
{
|
||||
SqlQuery = StrategicOpportunityQueryConstants.ExplorationTrackingSelectSql,
|
||||
Parameters =
|
||||
[
|
||||
new(":dischargedatetime_Start_0", new DateTime(2024, 1, 1)),
|
||||
new(":dischargedatetime_End_1", new DateTime(2024, 12, 31)),
|
||||
new(":explorationpopulationid_2", TestExplorationPopulationId),
|
||||
new(":dischargedatetime_Start_3", new DateTime(2024, 1, 1)),
|
||||
new(":dischargedatetime_End_4", new DateTime(2024, 12, 31)),
|
||||
new(":explorationpopulationid_5", TestExplorationPopulationId),
|
||||
]
|
||||
})
|
||||
.ReturnsAsync(new SqlResponse
|
||||
{
|
||||
SqlQuery = StrategicOpportunityQueryConstants.ExplorationMeasureSelectSql,
|
||||
Parameters =
|
||||
[
|
||||
new(":dischargedatetime_Start_0", new DateTime(2024, 1, 1)),
|
||||
new(":dischargedatetime_End_1", new DateTime(2024, 12, 31)),
|
||||
new(":explorationpopulationid_2", TestExplorationPopulationId),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupBuildSqlQueryForMonitoring()
|
||||
{
|
||||
_mockDataSchemaService.Setup(x =>
|
||||
x.BuildSqlQuery(It.IsAny<QueryConfig>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new SqlResponse
|
||||
{
|
||||
SqlQuery = StrategicOpportunityQueryConstants.MeasureSelectSql,
|
||||
Parameters =
|
||||
[
|
||||
new(":dischargedatetime_Start_0", new DateTime(2024, 1, 1)),
|
||||
new(":dischargedatetime_End_1", new DateTime(2024, 12, 31)),
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates that the SQL generated by StrategicOpportunityGLDetailQueryBuilder
|
||||
/// is syntactically correct and references valid objects in Snowflake.
|
||||
/// Uses EXPLAIN to parse and plan each statement without executing DML.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class StrategicOpportunityGLQueryBuilderIntegrationTests : IntegrationTestBase
|
||||
{
|
||||
private StrategicOpportunityGLDetailQueryBuilder _queryBuilder;
|
||||
|
||||
private static readonly List<int> DepartmentIds = [1];
|
||||
private static readonly List<int> RevenueAccountIds = [1];
|
||||
private static readonly List<int> ExpenseAccountIds = [2];
|
||||
private static readonly List<int> StatisticAccountIds = [3];
|
||||
private static readonly DateOnly BaselineStart = new(2024, 1, 1);
|
||||
private static readonly DateOnly BaselineEnd = new(2024, 12, 31);
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_queryBuilder = new StrategicOpportunityGLDetailQueryBuilder();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task BuildInsertQuery_FwTable_IsValidSnowflakeSql()
|
||||
{
|
||||
var query = _queryBuilder.BuildInsertQuery(
|
||||
opportunityId: 0,
|
||||
sourceTable: "fw.factglsampled",
|
||||
departmentIds: DepartmentIds,
|
||||
revenueAccountIds: RevenueAccountIds,
|
||||
expenseAccountIds: ExpenseAccountIds,
|
||||
statisticAccountIds: StatisticAccountIds,
|
||||
baselineStart: BaselineStart,
|
||||
baselineEnd: BaselineEnd);
|
||||
|
||||
var act = async () => await SnowflakeDatabaseContext.QueryAsync<dynamic>($"EXPLAIN {query}", nameof(BuildInsertQuery_FwTable_IsValidSnowflakeSql));
|
||||
await act.Should().NotThrowAsync("the generated INSERT SQL should be valid Snowflake syntax");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task BuildInsertQuery_IntTable_IsValidSnowflakeSql()
|
||||
{
|
||||
var query = _queryBuilder.BuildInsertQuery(
|
||||
opportunityId: 0,
|
||||
sourceTable: "int.factgl",
|
||||
departmentIds: DepartmentIds,
|
||||
revenueAccountIds: RevenueAccountIds,
|
||||
expenseAccountIds: ExpenseAccountIds,
|
||||
statisticAccountIds: StatisticAccountIds,
|
||||
baselineStart: BaselineStart,
|
||||
baselineEnd: BaselineEnd);
|
||||
|
||||
var act = async () => await SnowflakeDatabaseContext.QueryAsync<dynamic>($"EXPLAIN {query}", nameof(BuildInsertQuery_IntTable_IsValidSnowflakeSql));
|
||||
await act.Should().NotThrowAsync("the generated INSERT SQL should be valid Snowflake syntax");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task BuildMergeQuery_FwTable_IsValidSnowflakeSql()
|
||||
{
|
||||
var query = _queryBuilder.BuildMergeQuery(
|
||||
opportunityId: 0,
|
||||
sourceTable: "fw.factglsampled",
|
||||
departmentIds: DepartmentIds,
|
||||
revenueAccountIds: RevenueAccountIds,
|
||||
expenseAccountIds: ExpenseAccountIds,
|
||||
statisticAccountIds: StatisticAccountIds,
|
||||
trackingStart: new DateOnly(2025, 1, 1),
|
||||
trackingEnd: new DateOnly(2025, 12, 31));
|
||||
|
||||
var act = async () => await SnowflakeDatabaseContext.QueryAsync<dynamic>($"EXPLAIN {query}", nameof(BuildMergeQuery_FwTable_IsValidSnowflakeSql));
|
||||
await act.Should().NotThrowAsync("the generated MERGE SQL should be valid Snowflake syntax");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task BuildDeleteQuery_WithOpportunityId_IsValidSnowflakeSql()
|
||||
{
|
||||
var query = _queryBuilder.BuildDeleteQuery(opportunityId: 0);
|
||||
|
||||
var act = async () => await SnowflakeDatabaseContext.QueryAsync<dynamic>($"EXPLAIN {query}", nameof(BuildDeleteQuery_WithOpportunityId_IsValidSnowflakeSql));
|
||||
await act.Should().NotThrowAsync("the generated DELETE SQL should be valid Snowflake syntax");
|
||||
}
|
||||
}
|
||||
}
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests
|
||||
{
|
||||
public static class StrategicOpportunityQueryConstants
|
||||
{
|
||||
public static readonly string TrackingSelectSql = @"SELECT
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup"",
|
||||
ZEROIFNULL(""Units"") AS ""Units"",
|
||||
ZEROIFNULL(""Cost"") AS ""Cost""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup"",
|
||||
SUM(""Units"") AS ""Units"",
|
||||
SUM(""Cost"") AS ""Cost""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
DSSPES.encounterid AS ""dsspesEncounterId"",
|
||||
DSSPES.dischargedatetime AS ""dsspesDischargeDateTime"",
|
||||
DSSPES.entityid AS ""dsspesEntityID"",
|
||||
DSSPES.primaryphysicianid AS ""dsspesPhysicianId"",
|
||||
DSSPES.servicelineid AS ""dsspesPrimaryServiceLineId"",
|
||||
DSSPES.cptcodecasetypefamilyid AS ""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
DSSPES.msdrgcasetypefamilyid AS ""dsspesMSDRGCaseTypeFamilyId"",
|
||||
DSSPES.aprdrgcasetypefamilyid AS ""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
PBLID.departmentid AS ""pblidDepartmentId"",
|
||||
PHYSPRIMARY.physicianspecialtyid AS ""physPrimaryPhysicianSpecialtyId"",
|
||||
CC.chargecodeid AS ""ccChargeCodeID"",
|
||||
IFNULL(CC.costdriver::TEXT, '') AS ""ccCostDriver"",
|
||||
IFNULL(ENCOUNTERPATIENTTYPEROLLUP.patienttyperollup::TEXT, '') AS ""encounterPatientTypeRollupPatientTypeRollup"",
|
||||
NULL AS ""Cost"",
|
||||
SUM(PBLID.unitsofservice) AS ""Units""
|
||||
|
||||
FROM dss.FactPatientBillingLineItemDetail AS PBLID
|
||||
RIGHT JOIN clientdss.FactPatientEncounterSummary AS DSSPES ON DSSPES.encounterid = PBLID.encounterid
|
||||
LEFT JOIN dss.FactPatientEncounterClinicalIndicator AS DSSPECI ON DSSPECI.encounterid = PBLID.encounterid
|
||||
LEFT JOIN CCI.VIEWENCOUNTERPATIENTTYPEROLLUP AS ENCOUNTERPATIENTTYPEROLLUP ON ENCOUNTERPATIENTTYPEROLLUP.encounterid = PBLID.encounterid
|
||||
INNER JOIN FW.DIMCHARGECODE AS CC ON CC.chargecodeid = PBLID.chargecodeid
|
||||
INNER JOIN DSS.DIMPHYSICIAN AS PHYSPRIMARY ON PHYSPRIMARY.physicianid = DSSPES.primaryphysicianid
|
||||
|
||||
WHERE
|
||||
((DSSPES.dischargedatetime >= :dischargedatetime_Start_0 AND DSSPES.dischargedatetime <= :dischargedatetime_End_1) AND DSSPECI.clinicalindicatorid = :clinicalindicatorid_2)
|
||||
|
||||
GROUP BY
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup""
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
DSSPES.encounterid AS ""dsspesEncounterId"",
|
||||
DSSPES.dischargedatetime AS ""dsspesDischargeDateTime"",
|
||||
DSSPES.entityid AS ""dsspesEntityID"",
|
||||
DSSPES.primaryphysicianid AS ""dsspesPhysicianId"",
|
||||
DSSPES.servicelineid AS ""dsspesPrimaryServiceLineId"",
|
||||
DSSPES.cptcodecasetypefamilyid AS ""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
DSSPES.msdrgcasetypefamilyid AS ""dsspesMSDRGCaseTypeFamilyId"",
|
||||
DSSPES.aprdrgcasetypefamilyid AS ""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
PBLID.departmentid AS ""pblidDepartmentId"",
|
||||
PHYSPRIMARY.physicianspecialtyid AS ""physPrimaryPhysicianSpecialtyId"",
|
||||
CC.chargecodeid AS ""ccChargeCodeID"",
|
||||
IFNULL(CC.costdriver::TEXT, '') AS ""ccCostDriver"",
|
||||
IFNULL(ENCOUNTERPATIENTTYPEROLLUP.patienttyperollup::TEXT, '') AS ""encounterPatientTypeRollupPatientTypeRollup"",
|
||||
SUM(COSTDETAIL.variabledirectcost) AS ""Cost"",
|
||||
NULL AS ""Units""
|
||||
|
||||
FROM dss.FactPatientCostDetail AS COSTDETAIL
|
||||
RIGHT JOIN dss.FactPatientBillingLineItemDetail AS PBLID ON PBLID.rowid = COSTDETAIL.pblidrowid
|
||||
RIGHT JOIN clientdss.FactPatientEncounterSummary AS DSSPES ON DSSPES.encounterid = PBLID.encounterid
|
||||
LEFT JOIN dss.FactPatientEncounterClinicalIndicator AS DSSPECI ON DSSPECI.encounterid = PBLID.encounterid
|
||||
LEFT JOIN CCI.VIEWENCOUNTERPATIENTTYPEROLLUP AS ENCOUNTERPATIENTTYPEROLLUP ON ENCOUNTERPATIENTTYPEROLLUP.encounterid = PBLID.encounterid
|
||||
INNER JOIN FW.DIMCHARGECODE AS CC ON CC.chargecodeid = PBLID.chargecodeid
|
||||
INNER JOIN DSS.DIMPHYSICIAN AS PHYSPRIMARY ON PHYSPRIMARY.physicianid = DSSPES.primaryphysicianid
|
||||
|
||||
WHERE
|
||||
((DSSPES.dischargedatetime >= :dischargedatetime_Start_3 AND DSSPES.dischargedatetime <= :dischargedatetime_End_4) AND DSSPECI.clinicalindicatorid = :clinicalindicatorid_5)
|
||||
|
||||
GROUP BY
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup""
|
||||
|
||||
|
||||
)
|
||||
|
||||
GROUP BY
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup""
|
||||
|
||||
|
||||
)
|
||||
|
||||
ORDER BY
|
||||
""dsspesEncounterId"" ASC NULLS FIRST,
|
||||
""dsspesDischargeDateTime"" ASC NULLS FIRST,
|
||||
""dsspesEntityID"" ASC NULLS FIRST,
|
||||
""dsspesPhysicianId"" ASC NULLS FIRST,
|
||||
""dsspesPrimaryServiceLineId"" ASC NULLS FIRST,
|
||||
""dsspesCPTCodeCaseTypeFamilyId"" ASC NULLS FIRST,
|
||||
""dsspesMSDRGCaseTypeFamilyId"" ASC NULLS FIRST,
|
||||
""dsspesAPRDRGCaseTypeFamilyId"" ASC NULLS FIRST,
|
||||
""pblidDepartmentId"" ASC NULLS FIRST,
|
||||
""physPrimaryPhysicianSpecialtyId"" ASC NULLS FIRST,
|
||||
""ccChargeCodeID"" ASC NULLS FIRST,
|
||||
""ccCostDriver"" ASC NULLS FIRST,
|
||||
""encounterPatientTypeRollupPatientTypeRollup"" ASC NULLS FIRST
|
||||
";
|
||||
|
||||
// SQL returned by BuildSqlQuery for the measure detail query (PopulateMeasureDetails, line 215).
|
||||
// Uses real column names from clientdss.FactPatientEncounterSummary and dss.FactPatientBillingLineItemDetail
|
||||
// that exist in the test client's Snowflake database.
|
||||
public static readonly string MeasureSelectSql = @"SELECT
|
||||
""DischargeDateTime"",
|
||||
ZEROIFNULL(""Value"") AS ""Value""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
""DischargeDateTime"",
|
||||
SUM(""Value"") AS ""Value""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
DSSPES.dischargedatetime AS ""DischargeDateTime"",
|
||||
SUM(PBLID.unitsofservice) AS ""Value""
|
||||
|
||||
FROM dss.FactPatientBillingLineItemDetail AS PBLID
|
||||
RIGHT JOIN clientdss.FactPatientEncounterSummary AS DSSPES ON DSSPES.encounterid = PBLID.encounterid
|
||||
|
||||
WHERE
|
||||
(DSSPES.dischargedatetime >= :dischargedatetime_Start_0 AND DSSPES.dischargedatetime <= :dischargedatetime_End_1)
|
||||
|
||||
GROUP BY
|
||||
""DischargeDateTime""
|
||||
|
||||
|
||||
)
|
||||
|
||||
GROUP BY
|
||||
""DischargeDateTime""
|
||||
|
||||
|
||||
)
|
||||
|
||||
ORDER BY
|
||||
""DischargeDateTime"" ASC NULLS FIRST
|
||||
";
|
||||
|
||||
// SQL for the tracking merge when opportunity uses an ExplorationPopulation instead of PatientPopulationHPath.
|
||||
// Contains LEFT JOIN CCI.FACTEXPLORATIONPOPULATIONENCOUNTER which ApplyExplorationFilters replaces
|
||||
// with an INNER JOIN subquery at runtime.
|
||||
public static readonly string ExplorationTrackingSelectSql = @"SELECT
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup"",
|
||||
ZEROIFNULL(""Units"") AS ""Units"",
|
||||
ZEROIFNULL(""Cost"") AS ""Cost""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup"",
|
||||
SUM(""Units"") AS ""Units"",
|
||||
SUM(""Cost"") AS ""Cost""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
DSSPES.encounterid AS ""dsspesEncounterId"",
|
||||
DSSPES.dischargedatetime AS ""dsspesDischargeDateTime"",
|
||||
DSSPES.entityid AS ""dsspesEntityID"",
|
||||
DSSPES.primaryphysicianid AS ""dsspesPhysicianId"",
|
||||
DSSPES.servicelineid AS ""dsspesPrimaryServiceLineId"",
|
||||
DSSPES.cptcodecasetypefamilyid AS ""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
DSSPES.msdrgcasetypefamilyid AS ""dsspesMSDRGCaseTypeFamilyId"",
|
||||
DSSPES.aprdrgcasetypefamilyid AS ""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
PBLID.departmentid AS ""pblidDepartmentId"",
|
||||
PHYSPRIMARY.physicianspecialtyid AS ""physPrimaryPhysicianSpecialtyId"",
|
||||
CC.chargecodeid AS ""ccChargeCodeID"",
|
||||
IFNULL(CC.costdriver::TEXT, '') AS ""ccCostDriver"",
|
||||
IFNULL(ENCOUNTERPATIENTTYPEROLLUP.patienttyperollup::TEXT, '') AS ""encounterPatientTypeRollupPatientTypeRollup"",
|
||||
NULL AS ""Cost"",
|
||||
SUM(PBLID.unitsofservice) AS ""Units""
|
||||
|
||||
FROM dss.FactPatientBillingLineItemDetail AS PBLID
|
||||
RIGHT JOIN clientdss.FactPatientEncounterSummary AS DSSPES ON DSSPES.encounterid = PBLID.encounterid
|
||||
LEFT JOIN CCI.FACTEXPLORATIONPOPULATIONENCOUNTER AS EXPLORATIONPOPULATIONENCOUNTER ON EXPLORATIONPOPULATIONENCOUNTER.encounterid = PBLID.encounterid
|
||||
LEFT JOIN CCI.VIEWENCOUNTERPATIENTTYPEROLLUP AS ENCOUNTERPATIENTTYPEROLLUP ON ENCOUNTERPATIENTTYPEROLLUP.encounterid = PBLID.encounterid
|
||||
INNER JOIN FW.DIMCHARGECODE AS CC ON CC.chargecodeid = PBLID.chargecodeid
|
||||
INNER JOIN DSS.DIMPHYSICIAN AS PHYSPRIMARY ON PHYSPRIMARY.physicianid = DSSPES.primaryphysicianid
|
||||
|
||||
WHERE
|
||||
((DSSPES.dischargedatetime >= :dischargedatetime_Start_0 AND DSSPES.dischargedatetime <= :dischargedatetime_End_1) AND EXPLORATIONPOPULATIONENCOUNTER.explorationpopulationid = :explorationpopulationid_2)
|
||||
|
||||
GROUP BY
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup""
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
DSSPES.encounterid AS ""dsspesEncounterId"",
|
||||
DSSPES.dischargedatetime AS ""dsspesDischargeDateTime"",
|
||||
DSSPES.entityid AS ""dsspesEntityID"",
|
||||
DSSPES.primaryphysicianid AS ""dsspesPhysicianId"",
|
||||
DSSPES.servicelineid AS ""dsspesPrimaryServiceLineId"",
|
||||
DSSPES.cptcodecasetypefamilyid AS ""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
DSSPES.msdrgcasetypefamilyid AS ""dsspesMSDRGCaseTypeFamilyId"",
|
||||
DSSPES.aprdrgcasetypefamilyid AS ""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
PBLID.departmentid AS ""pblidDepartmentId"",
|
||||
PHYSPRIMARY.physicianspecialtyid AS ""physPrimaryPhysicianSpecialtyId"",
|
||||
CC.chargecodeid AS ""ccChargeCodeID"",
|
||||
IFNULL(CC.costdriver::TEXT, '') AS ""ccCostDriver"",
|
||||
IFNULL(ENCOUNTERPATIENTTYPEROLLUP.patienttyperollup::TEXT, '') AS ""encounterPatientTypeRollupPatientTypeRollup"",
|
||||
SUM(COSTDETAIL.variabledirectcost) AS ""Cost"",
|
||||
NULL AS ""Units""
|
||||
|
||||
FROM dss.FactPatientCostDetail AS COSTDETAIL
|
||||
RIGHT JOIN dss.FactPatientBillingLineItemDetail AS PBLID ON PBLID.rowid = COSTDETAIL.pblidrowid
|
||||
RIGHT JOIN clientdss.FactPatientEncounterSummary AS DSSPES ON DSSPES.encounterid = PBLID.encounterid
|
||||
LEFT JOIN CCI.FACTEXPLORATIONPOPULATIONENCOUNTER AS EXPLORATIONPOPULATIONENCOUNTER ON EXPLORATIONPOPULATIONENCOUNTER.encounterid = PBLID.encounterid
|
||||
LEFT JOIN CCI.VIEWENCOUNTERPATIENTTYPEROLLUP AS ENCOUNTERPATIENTTYPEROLLUP ON ENCOUNTERPATIENTTYPEROLLUP.encounterid = PBLID.encounterid
|
||||
INNER JOIN FW.DIMCHARGECODE AS CC ON CC.chargecodeid = PBLID.chargecodeid
|
||||
INNER JOIN DSS.DIMPHYSICIAN AS PHYSPRIMARY ON PHYSPRIMARY.physicianid = DSSPES.primaryphysicianid
|
||||
|
||||
WHERE
|
||||
((DSSPES.dischargedatetime >= :dischargedatetime_Start_3 AND DSSPES.dischargedatetime <= :dischargedatetime_End_4) AND EXPLORATIONPOPULATIONENCOUNTER.explorationpopulationid = :explorationpopulationid_5)
|
||||
|
||||
GROUP BY
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup""
|
||||
|
||||
|
||||
)
|
||||
|
||||
GROUP BY
|
||||
""dsspesEncounterId"",
|
||||
""dsspesDischargeDateTime"",
|
||||
""dsspesEntityID"",
|
||||
""dsspesPhysicianId"",
|
||||
""dsspesPrimaryServiceLineId"",
|
||||
""dsspesCPTCodeCaseTypeFamilyId"",
|
||||
""dsspesMSDRGCaseTypeFamilyId"",
|
||||
""dsspesAPRDRGCaseTypeFamilyId"",
|
||||
""pblidDepartmentId"",
|
||||
""physPrimaryPhysicianSpecialtyId"",
|
||||
""ccChargeCodeID"",
|
||||
""ccCostDriver"",
|
||||
""encounterPatientTypeRollupPatientTypeRollup""
|
||||
|
||||
|
||||
)
|
||||
|
||||
ORDER BY
|
||||
""dsspesEncounterId"" ASC NULLS FIRST,
|
||||
""dsspesDischargeDateTime"" ASC NULLS FIRST,
|
||||
""dsspesEntityID"" ASC NULLS FIRST,
|
||||
""dsspesPhysicianId"" ASC NULLS FIRST,
|
||||
""dsspesPrimaryServiceLineId"" ASC NULLS FIRST,
|
||||
""dsspesCPTCodeCaseTypeFamilyId"" ASC NULLS FIRST,
|
||||
""dsspesMSDRGCaseTypeFamilyId"" ASC NULLS FIRST,
|
||||
""dsspesAPRDRGCaseTypeFamilyId"" ASC NULLS FIRST,
|
||||
""pblidDepartmentId"" ASC NULLS FIRST,
|
||||
""physPrimaryPhysicianSpecialtyId"" ASC NULLS FIRST,
|
||||
""ccChargeCodeID"" ASC NULLS FIRST,
|
||||
""ccCostDriver"" ASC NULLS FIRST,
|
||||
""encounterPatientTypeRollupPatientTypeRollup"" ASC NULLS FIRST
|
||||
";
|
||||
|
||||
// SQL for exploration-path tests (instead of clinical indicator)
|
||||
public static readonly string ExplorationMeasureSelectSql = @"SELECT
|
||||
""DischargeDateTime"",
|
||||
ZEROIFNULL(""Value"") AS ""Value""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
""DischargeDateTime"",
|
||||
SUM(""Value"") AS ""Value""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
DSSPES.dischargedatetime AS ""DischargeDateTime"",
|
||||
SUM(PBLID.unitsofservice) AS ""Value""
|
||||
|
||||
FROM dss.FactPatientBillingLineItemDetail AS PBLID
|
||||
RIGHT JOIN clientdss.FactPatientEncounterSummary AS DSSPES ON DSSPES.encounterid = PBLID.encounterid
|
||||
LEFT JOIN CCI.FACTEXPLORATIONPOPULATIONENCOUNTER AS EXPLORATIONPOPULATIONENCOUNTER ON EXPLORATIONPOPULATIONENCOUNTER.encounterid = PBLID.encounterid
|
||||
|
||||
WHERE
|
||||
((DSSPES.dischargedatetime >= :dischargedatetime_Start_0 AND DSSPES.dischargedatetime <= :dischargedatetime_End_1) AND EXPLORATIONPOPULATIONENCOUNTER.explorationpopulationid = :explorationpopulationid_2)
|
||||
|
||||
GROUP BY
|
||||
""DischargeDateTime""
|
||||
|
||||
|
||||
)
|
||||
|
||||
GROUP BY
|
||||
""DischargeDateTime""
|
||||
|
||||
|
||||
)
|
||||
|
||||
ORDER BY
|
||||
""DischargeDateTime"" ASC NULLS FIRST
|
||||
";
|
||||
}
|
||||
}
|
||||
+653
@@ -0,0 +1,653 @@
|
||||
using FluentAssertions;
|
||||
using Hangfire;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moq;
|
||||
using Strata.ContinuousImprovement.Biz.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment;
|
||||
using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks;
|
||||
using Strata.ContinuousImprovement.Biz.Security;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums;
|
||||
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
|
||||
using Strata.CoreLib.Claims.Extensions;
|
||||
using Strata.DataSchema.Client;
|
||||
using Strata.DataSchema.Models.Query;
|
||||
using Strata.DataSchema.Models.Schema;
|
||||
using Strata.Id.Client;
|
||||
using Strata.Schema.Client;
|
||||
using Strata.Schema.Client.Dtos;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[Ignore("temporarily ignoring because schema is out of sync")]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class StrategicOpportunityServiceTests : IntegrationTestBase
|
||||
{
|
||||
private const string POPULATION_NAME = "Mocked Population Name";
|
||||
private IStrategicOpportunityService _strategicOpportunityService;
|
||||
private IOpportunityRoleAssignmentService _opportunityRoleAssignmentService;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
// Arrange mocks
|
||||
var mockSchemaServiceClient = new Mock<ISchemaServiceClient>();
|
||||
var mockDataSchemaService = new Mock<IDataSchemaService>();
|
||||
var mockIdServiceClient = new Mock<IIdServiceClient>();
|
||||
var mockIdRollupColumn = new Mock<IInitiativeRollupColumnService>();
|
||||
var mockOpportunityWorkbookService = new Mock<IOpportunityWorkbookService>();
|
||||
var mockStrategicOpportunityDetailService = new Mock<IStrategicChargeCodeOpportunityDetailService>();
|
||||
var mockStrategicGLOpportunityDetailService = new Mock<IStrategicGLOpportunityDetailService>();
|
||||
var mockSimpleSecurityServiceClient = new Mock<ISimpleSecurityService>();
|
||||
var mockOpportunityRoleAssignmentServiceClient = new Mock<IOpportunityRoleAssignmentService>();
|
||||
var mockBackgroundJobClient = new Mock<IBackgroundJobClient>();
|
||||
var mockDistributionProcessFactory = new Mock<IDistributionProcessFactory>();
|
||||
|
||||
// Add mock responses
|
||||
mockDataSchemaService.Setup(x =>
|
||||
x.GetDataTableBySqlFullNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new DataTable());
|
||||
|
||||
mockDataSchemaService.Setup(x =>
|
||||
x.GetDataColumnsBySqlColumnNameAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync([]);
|
||||
|
||||
mockDataSchemaService.Setup(x =>
|
||||
x.BuildSqlQuery(It.IsAny<QueryConfig>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new SqlResponse
|
||||
{
|
||||
SqlQuery = DataSchemaServiceSelectSql,
|
||||
Parameters = new List<KeyValuePair<string, object>>
|
||||
{
|
||||
new(":dischargedatetime_Start_0", new DateTime(2024, 1, 1)),
|
||||
new(":dischargedatetime_End_1", new DateTime(2024, 12, 31)),
|
||||
new(":chargecodeid_2", 3541)
|
||||
}
|
||||
});
|
||||
|
||||
mockSchemaServiceClient.Setup(x =>
|
||||
x.GetDimensionByGlobalIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new Schema.Client.Dtos.Info.ScoreDimensionInfoDto());
|
||||
|
||||
var list = new List<HierarchyNodeDto>();
|
||||
list.Add(new HierarchyNodeDto
|
||||
{
|
||||
Path = "CLINICALINDICATOR|CLINICALINDI|188",
|
||||
Name = POPULATION_NAME
|
||||
});
|
||||
|
||||
mockSchemaServiceClient.Setup(x =>
|
||||
x.GetHierarchyNodesFromHierarchyPathsAsync(It.IsAny<Guid>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(list);
|
||||
|
||||
mockSchemaServiceClient.Setup(x =>
|
||||
x.GetDimensionMembersFromHierarchyPathsAsync(It.IsAny<Guid>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<MemberDto> {
|
||||
new MemberDto
|
||||
{
|
||||
Id = "188",
|
||||
Name = "All Readmissions"
|
||||
}});
|
||||
|
||||
var claimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
|
||||
_opportunityRoleAssignmentService = new OpportunityRoleAssignmentService(
|
||||
(DbContexts.CentralDbContext)CentralDbContext,
|
||||
mockIdServiceClient.Object,
|
||||
mockSimpleSecurityServiceClient.Object,
|
||||
claimsPrincipalAccessor
|
||||
);
|
||||
|
||||
_strategicOpportunityService = new StrategicOpportunityService(
|
||||
JazzConnBuilderFactory,
|
||||
(DbContexts.CentralDbContext)CentralDbContext,
|
||||
claimsPrincipalAccessor,
|
||||
mockSchemaServiceClient.Object,
|
||||
SnowflakeDatabaseContext,
|
||||
mockIdServiceClient.Object,
|
||||
_opportunityRoleAssignmentService,
|
||||
mockIdRollupColumn.Object,
|
||||
mockOpportunityWorkbookService.Object,
|
||||
mockBackgroundJobClient.Object,
|
||||
mockStrategicOpportunityDetailService.Object,
|
||||
new StrategicItemDimensionSyncService(JazzConnBuilderFactory, (DbContexts.CentralDbContext)CentralDbContext, SnowflakeDatabaseContext),
|
||||
mockStrategicGLOpportunityDetailService.Object,
|
||||
(Microsoft.Extensions.Logging.ILogger<StrategicOpportunityService>)logger);
|
||||
}
|
||||
|
||||
[Test, Ignore("temporarily ignoring because schema is out of sync")]
|
||||
public async Task CreatingOpportunityShouldAlsoCreateDetails()
|
||||
{
|
||||
//arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
|
||||
//act
|
||||
var result = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
//assert
|
||||
result.Should().Be(1, "On success should return opportunity id.");
|
||||
}
|
||||
|
||||
private static StrategicOpportunityChargeCodeDto CreateOpportunityDto()
|
||||
{
|
||||
var opportunity = new StrategicOpportunityChargeCodeDto()
|
||||
{
|
||||
Name = "Integration-StrategicOpportunityChargeCode",
|
||||
OpportunityType = StrategicOpportunityTypes.ChargeCode,
|
||||
OpportunityFiltersJSON = "[]",
|
||||
Rollup1Id = 0,
|
||||
Rollup2Id = 0,
|
||||
Rollup3Id = 0,
|
||||
BaselineType = BaselineType.MonthRange,
|
||||
BaselineDistribution = DistributionMethod.Average,
|
||||
BaselineStartDate = new DateOnly(2024, 1, 1),
|
||||
BaselineEndDate = new DateOnly(2024, 12, 31),
|
||||
EstimatedTrackingDuration = 12,
|
||||
|
||||
// Assume empty or test collections for these
|
||||
Measures = Enumerable.Empty<StrategicOpportunityMeasureDto>(),
|
||||
WorkbookIds = Enumerable.Empty<int>(),
|
||||
|
||||
// ChargeCode-specific properties
|
||||
PatientPopulationHPath = "CLINICALINDICATOR|CLINICALINDI|188",
|
||||
ExplorationPopulationId = 0,
|
||||
ChargeCodeHPath = "CC|CCRU|Blood"
|
||||
};
|
||||
return opportunity;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateStrategicOpportunityRampUpsAsync_ShouldUpdateRampUps()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
var rampUps = new List<StrategicOpportunityRampUpDto>
|
||||
{
|
||||
new() { OpportunityId = opportunityId, MonthNumber = 1, Percentage = 0.1 },
|
||||
new() { OpportunityId = opportunityId, MonthNumber = 2, Percentage = 0.2 }
|
||||
};
|
||||
|
||||
// Act
|
||||
var updateResult = await _strategicOpportunityService.UpdateStrategicOpportunityRampUpsAsync(opportunityId, rampUps, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
updateResult.Should().BeTrue();
|
||||
|
||||
// Verify persisted values
|
||||
var retrieved = await _strategicOpportunityService.GetStrategicOpportunityRampUpsAsync(opportunityId, CancellationToken.None);
|
||||
retrieved.Should().HaveCount(rampUps.Count);
|
||||
retrieved[0].MonthNumber.Should().Be(rampUps[0].MonthNumber);
|
||||
retrieved[0].Percentage.Should().Be(rampUps[0].Percentage);
|
||||
retrieved[1].MonthNumber.Should().Be(rampUps[1].MonthNumber);
|
||||
retrieved[1].Percentage.Should().Be(rampUps[1].Percentage);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ResetStrategicOpportunityRampUps_ShouldResetRampUps()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
var rampUps = new List<StrategicOpportunityRampUpDto>
|
||||
{
|
||||
new() { OpportunityId = opportunityId, MonthNumber = 1, Percentage = 0.1 },
|
||||
new() { OpportunityId = opportunityId, MonthNumber = 2, Percentage = 0.2 }
|
||||
};
|
||||
|
||||
await _strategicOpportunityService.UpdateStrategicOpportunityRampUpsAsync(opportunityId, rampUps, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var resetResult = await _strategicOpportunityService.ResetStrategicOpportunityRampUps(opportunityId, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
resetResult.Should().BeTrue();
|
||||
var retrieved = await _strategicOpportunityService.GetStrategicOpportunityRampUpsAsync(opportunityId, CancellationToken.None);
|
||||
|
||||
// Verify persisted values
|
||||
retrieved.Should().HaveCount(rampUps.Count);
|
||||
retrieved[0].Percentage.Should().Be(1);
|
||||
retrieved[1].Percentage.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ResetStrategicOpportunityRampUps_ShouldNotResetRampUps()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var resetResult = await _strategicOpportunityService.ResetStrategicOpportunityRampUps(opportunityId, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
resetResult.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateStrategicOpportunityRoleAssignment_ShouldUpdateRoleAssignments()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
var roleAssignments = new List<OpportunityRoleAssignmentView>()
|
||||
{
|
||||
new OpportunityRoleAssignmentView() { LootId = opportunityId, IdentityGuid = Guid.NewGuid(), IdentityName = "Test", CanRead = true, CanWrite = true, CanSecure = false }
|
||||
};
|
||||
|
||||
// Act
|
||||
var saveResult = await _opportunityRoleAssignmentService.SaveRoleAssignmentsAsync(opportunityId, roleAssignments, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
saveResult.Should().BeTrue();
|
||||
|
||||
// Verify persisted values
|
||||
var retrieved = await _opportunityRoleAssignmentService.GetRoleAssignmentsAsync(opportunityId, CancellationToken.None);
|
||||
var userPermission = await _opportunityRoleAssignmentService.GetUserRoleAssignmentsAsync(opportunityId, CancellationToken.None);
|
||||
|
||||
retrieved.Should().HaveCount(roleAssignments.Count);
|
||||
var item = retrieved.FirstOrDefault();
|
||||
item.LootId.Should().Be(opportunityId);
|
||||
item.IdentityGuid.Should().Be(roleAssignments[0].IdentityGuid);
|
||||
item.IdentityName.Should().Be(roleAssignments[0].IdentityName);
|
||||
item.CanRead.Should().Be(roleAssignments[0].CanRead);
|
||||
item.CanWrite.Should().Be(roleAssignments[0].CanWrite);
|
||||
item.CanSecure.Should().Be(roleAssignments[0].CanSecure);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetStrategicOpportunityRampUpsAsync_ShouldReturnEmptyListIfNoneExist()
|
||||
{
|
||||
// Arrange
|
||||
var nonExistentOpportunityId = long.MaxValue;
|
||||
|
||||
// Act
|
||||
var rampUps = await _strategicOpportunityService.GetStrategicOpportunityRampUpsAsync(nonExistentOpportunityId, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
rampUps.Should().NotBeNull();
|
||||
rampUps.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetStrategicOpportunitiesAsync_ShouldReturnOpportunitiesWithRollupsAndPopulation()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var results = await _strategicOpportunityService.GetStrategicOpportunitiesAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
results.Should().NotBeEmpty("At least one opportunity should be returned");
|
||||
var createdOpportunity = results.FirstOrDefault(o => o.OpportunityId == opportunityId);
|
||||
createdOpportunity.Should().NotBeNull("Created opportunity should be found in results");
|
||||
|
||||
createdOpportunity.OpportunityId.Should().Be(opportunityId);
|
||||
createdOpportunity.Name.Should().Be(opportunity.Name);
|
||||
createdOpportunity.OpportunityTypeName.Should().Be("Charge Code");
|
||||
createdOpportunity.EncounterGroupName.Should().Be("Patient Population", "When PatientPopulationHPath is set, it should be Patient Population");
|
||||
createdOpportunity.AdditionalFilters.Should().Be("No");
|
||||
createdOpportunity.BaselinePeriod.Should()
|
||||
.Be($"{opportunity.BaselineStartDate:MMM d, yyyy} - {opportunity.BaselineEndDate:MMM d, yyyy}");
|
||||
createdOpportunity.EstimatedTrackDurationMonths.Should().Be(opportunity.EstimatedTrackingDuration);
|
||||
|
||||
createdOpportunity.Rollup1.Should().BeEmpty("When rollup1Id is 0, rollup should be empty");
|
||||
createdOpportunity.Rollup2.Should().BeEmpty("When rollup2Id is 0, rollup should be empty");
|
||||
createdOpportunity.Rollup3.Should().BeEmpty("When rollup3Id is 0, rollup should be empty");
|
||||
|
||||
createdOpportunity.PatientPopulationHPath.Should().Be(opportunity.PatientPopulationHPath);
|
||||
createdOpportunity.ExplorationPopulationId.Should().Be(opportunity.ExplorationPopulationId);
|
||||
createdOpportunity.Population.Should().Be(POPULATION_NAME);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateStrategicOpportunityStatus_WhenNewInitiative_ShouldUpdateStatusAndDates()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
var updateStatusDto = new StrategicOpportunityUpdateStatusDto
|
||||
{
|
||||
Status = (byte)StrategicOpportunityStatus.NewInitiative,
|
||||
TrackingStartDate = new DateOnly(2024, 1, 1),
|
||||
TrackingEndDate = new DateOnly(2024, 12, 31)
|
||||
};
|
||||
|
||||
// Act
|
||||
var updateResult = await _strategicOpportunityService.UpdateStrategicOpportunityStatus(opportunityId, updateStatusDto, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
updateResult.Should().BeTrue("Update should be successful");
|
||||
|
||||
var strataId = TestUtilities.GetClaimsPrincipalAccessor()?.GetCurrentClaimsPrincipal().GetStrataId();
|
||||
var opportunityAfterUpdate = await CentralDbContext.StrategicOpportunities
|
||||
.FirstOrDefaultAsync(x => x.OpportunityId == opportunityId && x.StrataId == strataId, CancellationToken.None);
|
||||
|
||||
opportunityAfterUpdate.Should().NotBeNull("Updated opportunity should exist");
|
||||
opportunityAfterUpdate.Status.Should().Be(updateStatusDto.Status, "Status should be updated to NewInitiative");
|
||||
opportunityAfterUpdate.TrackingStartDate.Should().Be(updateStatusDto.TrackingStartDate, "TrackingStartDate should be updated");
|
||||
opportunityAfterUpdate.TrackingEndDate.Should().Be(updateStatusDto.TrackingEndDate, "TrackingEndDate should be updated");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateStrategicOpportunityStatus_WhenNewOpportunity_ShouldResetTrackingData()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
var toInitiativeDto = new StrategicOpportunityUpdateStatusDto
|
||||
{
|
||||
Status = (byte)StrategicOpportunityStatus.NewInitiative,
|
||||
TrackingStartDate = new DateOnly(2024, 1, 1),
|
||||
TrackingEndDate = new DateOnly(2024, 12, 31)
|
||||
};
|
||||
await _strategicOpportunityService.UpdateStrategicOpportunityStatus(opportunityId, toInitiativeDto, CancellationToken.None);
|
||||
|
||||
var toOpportunityDto = new StrategicOpportunityUpdateStatusDto
|
||||
{
|
||||
Status = (byte)StrategicOpportunityStatus.NewOpportunity
|
||||
};
|
||||
|
||||
// Act
|
||||
var updateResult = await _strategicOpportunityService.UpdateStrategicOpportunityStatus(opportunityId, toOpportunityDto, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
updateResult.Should().BeTrue("Update should be successful");
|
||||
|
||||
var strataId = TestUtilities.GetClaimsPrincipalAccessor()?.GetCurrentClaimsPrincipal().GetStrataId();
|
||||
var opportunityAfterUpdate = await CentralDbContext.StrategicOpportunityChargeCodes
|
||||
.FirstOrDefaultAsync(x => x.OpportunityId == opportunityId && x.StrataId == strataId, CancellationToken.None);
|
||||
|
||||
opportunityAfterUpdate.Should().NotBeNull("Updated opportunity should exist");
|
||||
opportunityAfterUpdate.Status.Should().Be((byte)StrategicOpportunityStatus.NewOpportunity, "Status should be reset to NewOpportunity");
|
||||
opportunityAfterUpdate.TrackingStartDate.Should().Be(default(DateOnly), "TrackingStartDate should be reset to default");
|
||||
opportunityAfterUpdate.TrackingEndDate.Should().Be(default(DateOnly), "TrackingEndDate should be reset to default");
|
||||
opportunityAfterUpdate.LastRunTrackingDate.Should().BeNull("LastRunTrackingDate should be cleared");
|
||||
opportunityAfterUpdate.LastRunTrackingStatus.Should().Be(RunTrackingStatus.None, "LastRunTrackingStatus should be reset to None");
|
||||
opportunityAfterUpdate.ProgressToDateActual.Should().Be(0, "ProgressToDateActual should be reset to 0");
|
||||
opportunityAfterUpdate.ProgressToDateCommitted.Should().Be(0, "ProgressToDateCommitted should be reset to 0");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateStrategicOpportunityStatus_WhenOtherStatus_ShouldOnlyUpdateStatus()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
var toInitiativeDto = new StrategicOpportunityUpdateStatusDto
|
||||
{
|
||||
Status = (byte)StrategicOpportunityStatus.NewInitiative,
|
||||
TrackingStartDate = new DateOnly(2024, 1, 1),
|
||||
TrackingEndDate = new DateOnly(2024, 12, 31)
|
||||
};
|
||||
await _strategicOpportunityService.UpdateStrategicOpportunityStatus(opportunityId, toInitiativeDto, CancellationToken.None);
|
||||
|
||||
var toInValidationDto = new StrategicOpportunityUpdateStatusDto
|
||||
{
|
||||
Status = (byte)StrategicOpportunityStatus.InValidation
|
||||
};
|
||||
|
||||
// Act
|
||||
var updateResult = await _strategicOpportunityService.UpdateStrategicOpportunityStatus(opportunityId, toInValidationDto, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
updateResult.Should().BeTrue("Update should be successful");
|
||||
|
||||
var strataId = TestUtilities.GetClaimsPrincipalAccessor()?.GetCurrentClaimsPrincipal().GetStrataId();
|
||||
var opportunityAfterUpdate = await CentralDbContext.StrategicOpportunityChargeCodes
|
||||
.FirstOrDefaultAsync(x => x.OpportunityId == opportunityId && x.StrataId == strataId, CancellationToken.None);
|
||||
|
||||
opportunityAfterUpdate.Should().NotBeNull("Updated opportunity should exist");
|
||||
opportunityAfterUpdate.Status.Should().Be((byte)StrategicOpportunityStatus.InValidation, "Status should be updated to InValidation");
|
||||
opportunityAfterUpdate.TrackingStartDate.Should().Be(toInitiativeDto.TrackingStartDate, "TrackingStartDate should be unchanged");
|
||||
opportunityAfterUpdate.TrackingEndDate.Should().Be(toInitiativeDto.TrackingEndDate, "TrackingEndDate should be unchanged");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateStrategicOpportunityStatus_WhenOpportunityNotFound_ShouldReturnFalse()
|
||||
{
|
||||
// Arrange
|
||||
var nonExistentOpportunityId = long.MaxValue;
|
||||
var updateStatusDto = new StrategicOpportunityUpdateStatusDto
|
||||
{
|
||||
Status = (byte)StrategicOpportunityStatus.NewOpportunity
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _strategicOpportunityService.UpdateStrategicOpportunityStatus(nonExistentOpportunityId, updateStatusDto, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse("Update should return false when the opportunity does not exist");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CopyStrategicOpportunityRoleAssignments_ShouldReturnEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var newOpportunityId = opportunityId + 1;
|
||||
await _opportunityRoleAssignmentService.CopyTo(opportunityId, newOpportunityId, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var newRoles = await _opportunityRoleAssignmentService.GetRoleAssignmentsAsync(newOpportunityId, CancellationToken.None);
|
||||
|
||||
newRoles.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CopyStrategicOpportunityRoleAssignments_ShouldReturnRoleAssignments()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateOpportunityDto();
|
||||
var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, CancellationToken.None);
|
||||
|
||||
var roleAssignments = new List<OpportunityRoleAssignmentView>()
|
||||
{
|
||||
new() { LootId = opportunityId, IdentityGuid = Guid.NewGuid(), IdentityName = "Test1", CanRead = true, CanWrite = false, CanSecure = true },
|
||||
new() { LootId = opportunityId, IdentityGuid = Guid.NewGuid(), IdentityName = "Test2", CanRead = false, CanWrite = true, CanSecure = false },
|
||||
};
|
||||
|
||||
await _opportunityRoleAssignmentService.SaveRoleAssignmentsAsync(opportunityId, roleAssignments, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var newOpportunityId = opportunityId + 1;
|
||||
await _opportunityRoleAssignmentService.CopyTo(opportunityId, newOpportunityId, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var newRoles = await _opportunityRoleAssignmentService.GetRoleAssignmentsAsync(newOpportunityId, CancellationToken.None);
|
||||
|
||||
// Verify persisted values
|
||||
var retrieved = newRoles.ToList();
|
||||
|
||||
retrieved.Should().HaveCount(roleAssignments.Count);
|
||||
CompareRoleAssignments(retrieved[0], roleAssignments[0]);
|
||||
CompareRoleAssignments(retrieved[1], roleAssignments[1]);
|
||||
}
|
||||
|
||||
private static void CompareRoleAssignments(OpportunityRoleAssignmentView item1, OpportunityRoleAssignmentView item2)
|
||||
{
|
||||
item1.IdentityGuid.Should().Be(item2.IdentityGuid);
|
||||
item1.IdentityName.Should().Be(item2.IdentityName);
|
||||
item1.CanRead.Should().Be(item2.CanRead);
|
||||
item1.CanWrite.Should().Be(item2.CanWrite);
|
||||
item1.CanSecure.Should().Be(item2.CanSecure);
|
||||
}
|
||||
|
||||
public string DataSchemaServiceSelectSql = @"SELECT
|
||||
""EncounterId"",
|
||||
""DischargeDateTime"",
|
||||
""EntityID"",
|
||||
""PhysicianId"",
|
||||
""PrimaryServiceLineId"",
|
||||
""CPTCodeCaseTypeFamilyId"",
|
||||
""MSDRGCaseTypeFamilyId"",
|
||||
""APRDRGCaseTypeFamilyId"",
|
||||
""DepartmentId"",
|
||||
""PhysicianSpecialtyId"",
|
||||
""ChargeCodeID"",
|
||||
""CostDriver"",
|
||||
""PatientTypeRollup"",
|
||||
ZEROIFNULL(""Units"") AS ""Units"",
|
||||
ZEROIFNULL(""Cost"") AS ""Cost""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
""EncounterId"",
|
||||
""DischargeDateTime"",
|
||||
""EntityID"",
|
||||
""PhysicianId"",
|
||||
""PrimaryServiceLineId"",
|
||||
""CPTCodeCaseTypeFamilyId"",
|
||||
""MSDRGCaseTypeFamilyId"",
|
||||
""APRDRGCaseTypeFamilyId"",
|
||||
""DepartmentId"",
|
||||
""PhysicianSpecialtyId"",
|
||||
""ChargeCodeID"",
|
||||
""CostDriver"",
|
||||
""PatientTypeRollup"",
|
||||
SUM(""Units"") AS ""Units"",
|
||||
SUM(""Cost"") AS ""Cost""
|
||||
|
||||
FROM (
|
||||
SELECT
|
||||
DSSPES.encounterid AS ""EncounterId"",
|
||||
DSSPES.dischargedatetime AS ""DischargeDateTime"",
|
||||
DSSPES.entityid AS ""EntityID"",
|
||||
DSSPES.primaryphysicianid AS ""PhysicianId"",
|
||||
DSSPES.servicelineid AS ""PrimaryServiceLineId"",
|
||||
DSSPES.cptcodecasetypefamilyid AS ""CPTCodeCaseTypeFamilyId"",
|
||||
DSSPES.msdrgcasetypefamilyid AS ""MSDRGCaseTypeFamilyId"",
|
||||
DSSPES.aprdrgcasetypefamilyid AS ""APRDRGCaseTypeFamilyId"",
|
||||
PBLID.departmentid AS ""DepartmentId"",
|
||||
PHYSPRIMARY.physicianspecialtyid AS ""PhysicianSpecialtyId"",
|
||||
CC.chargecodeid AS ""ChargeCodeID"",
|
||||
IFNULL(CC.costdriver::TEXT, '') AS ""CostDriver"",
|
||||
IFNULL(EncounterPatientTypeRollup.patienttyperollup::TEXT, '') AS ""PatientTypeRollup"",
|
||||
NULL AS ""Cost"",
|
||||
SUM(PBLID.unitsofservice) AS ""Units""
|
||||
|
||||
FROM dss.FactPatientBillingLineItemDetail AS PBLID
|
||||
INNER JOIN clientdss.FactPatientEncounterSummary AS DSSPES ON DSSPES.encounterrecordnumber = PBLID.encounterrecordnumber
|
||||
LEFT JOIN dss.FactPatientEncounterClinicalIndicator AS DSSPECI ON DSSPECI.encounterid = PBLID.encounterid
|
||||
LEFT JOIN CCI.VIEWENCOUNTERPATIENTTYPEROLLUP AS EncounterPatientTypeRollup ON EncounterPatientTypeRollup.encounterid = PBLID.encounterid
|
||||
INNER JOIN FW.DIMCHARGECODE AS CC ON CC.chargecodeid = PBLID.chargecodeid
|
||||
INNER JOIN DSS.DIMPHYSICIAN AS PHYSPRIMARY ON PHYSPRIMARY.physicianid = DSSPES.primaryphysicianid
|
||||
|
||||
WHERE
|
||||
((DSSPES.dischargedatetime >= :dischargedatetime_Start_0 AND DSSPES.dischargedatetime <= :dischargedatetime_End_1) AND CC.chargecodeid IN (:chargecodeid_2))
|
||||
|
||||
GROUP BY
|
||||
""EncounterId"",
|
||||
""DischargeDateTime"",
|
||||
""EntityID"",
|
||||
""PhysicianId"",
|
||||
""PrimaryServiceLineId"",
|
||||
""CPTCodeCaseTypeFamilyId"",
|
||||
""MSDRGCaseTypeFamilyId"",
|
||||
""APRDRGCaseTypeFamilyId"",
|
||||
""DepartmentId"",
|
||||
""PhysicianSpecialtyId"",
|
||||
""ChargeCodeID"",
|
||||
""CostDriver"",
|
||||
""PatientTypeRollup""
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
DSSPES.encounterid AS ""EncounterId"",
|
||||
DSSPES.dischargedatetime AS ""DischargeDateTime"",
|
||||
DSSPES.entityid AS ""EntityID"",
|
||||
DSSPES.primaryphysicianid AS ""PhysicianId"",
|
||||
DSSPES.servicelineid AS ""PrimaryServiceLineId"",
|
||||
DSSPES.cptcodecasetypefamilyid AS ""CPTCodeCaseTypeFamilyId"",
|
||||
DSSPES.msdrgcasetypefamilyid AS ""MSDRGCaseTypeFamilyId"",
|
||||
DSSPES.aprdrgcasetypefamilyid AS ""APRDRGCaseTypeFamilyId"",
|
||||
PBLID.departmentid AS ""DepartmentId"",
|
||||
PHYSPRIMARY.physicianspecialtyid AS ""PhysicianSpecialtyId"",
|
||||
CC.chargecodeid AS ""ChargeCodeID"",
|
||||
IFNULL(CC.costdriver::TEXT, '') AS ""CostDriver"",
|
||||
IFNULL(EncounterPatientTypeRollup.patienttyperollup::TEXT, '') AS ""PatientTypeRollup"",
|
||||
SUM(COSTDETAIL.variabledirectcost) AS ""Cost"",
|
||||
NULL AS ""Units""
|
||||
|
||||
FROM dss.FactPatientCostDetail AS COSTDETAIL
|
||||
RIGHT JOIN dss.FactPatientBillingLineItemDetail AS PBLID ON PBLID.rowid = COSTDETAIL.pblidrowid
|
||||
INNER JOIN clientdss.FactPatientEncounterSummary AS DSSPES ON DSSPES.encounterrecordnumber = PBLID.encounterrecordnumber
|
||||
LEFT JOIN dss.FactPatientEncounterClinicalIndicator AS DSSPECI ON DSSPECI.encounterid = PBLID.encounterid
|
||||
LEFT JOIN CCI.VIEWENCOUNTERPATIENTTYPEROLLUP AS EncounterPatientTypeRollup ON EncounterPatientTypeRollup.encounterid = PBLID.encounterid
|
||||
INNER JOIN FW.DIMCHARGECODE AS CC ON CC.chargecodeid = PBLID.chargecodeid
|
||||
INNER JOIN DSS.DIMPHYSICIAN AS PHYSPRIMARY ON PHYSPRIMARY.physicianid = DSSPES.primaryphysicianid
|
||||
|
||||
WHERE
|
||||
((DSSPES.dischargedatetime >= :dischargedatetime_Start_0 AND DSSPES.dischargedatetime <= :dischargedatetime_End_1) AND CC.chargecodeid IN (:chargecodeid_2))
|
||||
|
||||
GROUP BY
|
||||
""EncounterId"",
|
||||
""DischargeDateTime"",
|
||||
""EntityID"",
|
||||
""PhysicianId"",
|
||||
""PrimaryServiceLineId"",
|
||||
""CPTCodeCaseTypeFamilyId"",
|
||||
""MSDRGCaseTypeFamilyId"",
|
||||
""APRDRGCaseTypeFamilyId"",
|
||||
""DepartmentId"",
|
||||
""PhysicianSpecialtyId"",
|
||||
""ChargeCodeID"",
|
||||
""CostDriver"",
|
||||
""PatientTypeRollup""
|
||||
|
||||
|
||||
)
|
||||
|
||||
GROUP BY
|
||||
""EncounterId"",
|
||||
""DischargeDateTime"",
|
||||
""EntityID"",
|
||||
""PhysicianId"",
|
||||
""PrimaryServiceLineId"",
|
||||
""CPTCodeCaseTypeFamilyId"",
|
||||
""MSDRGCaseTypeFamilyId"",
|
||||
""APRDRGCaseTypeFamilyId"",
|
||||
""DepartmentId"",
|
||||
""PhysicianSpecialtyId"",
|
||||
""ChargeCodeID"",
|
||||
""CostDriver"",
|
||||
""PatientTypeRollup""
|
||||
|
||||
|
||||
)
|
||||
|
||||
ORDER BY
|
||||
""EncounterId"" ASC NULLS FIRST,
|
||||
""DischargeDateTime"" ASC NULLS FIRST,
|
||||
""EntityID"" ASC NULLS FIRST,
|
||||
""PhysicianId"" ASC NULLS FIRST,
|
||||
""PrimaryServiceLineId"" ASC NULLS FIRST,
|
||||
""CPTCodeCaseTypeFamilyId"" ASC NULLS FIRST,
|
||||
""MSDRGCaseTypeFamilyId"" ASC NULLS FIRST,
|
||||
""APRDRGCaseTypeFamilyId"" ASC NULLS FIRST,
|
||||
""DepartmentId"" ASC NULLS FIRST,
|
||||
""PhysicianSpecialtyId"" ASC NULLS FIRST,
|
||||
""ChargeCodeID"" ASC NULLS FIRST,
|
||||
""CostDriver"" ASC NULLS FIRST,
|
||||
""PatientTypeRollup"" ASC NULLS FIRST
|
||||
|
||||
";
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using Strata.ContinuousImprovement.Biz.Exploration;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies;
|
||||
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using static Strata.ContinuousImprovement.Biz.Exploration.ExplorationService;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class TestSnowflakeDatabaseContext : IntegrationTestBase
|
||||
{
|
||||
[Test, Ignore("Test database needs to be rolled over to the new year")]
|
||||
public async Task BuildCaseTypeFamilySummaryQueryTest()
|
||||
{
|
||||
var queryParams = new QueryParams
|
||||
{
|
||||
CodeType = UtilizationVariation.VariationCaseTypes.CaseTypeCategories.MSDRG
|
||||
};
|
||||
|
||||
int year = DateTime.Now.Year;
|
||||
var dischargeDate = new FilterChipItem
|
||||
{
|
||||
ChipType = ChipType.DateRange,
|
||||
Filters = [],
|
||||
DateRange = [new DateTime(year, 1, 1), new DateTime(year + 1, 1, 1).AddTicks(-1)]
|
||||
};
|
||||
|
||||
var filters = new Exploration.Filters.Filter
|
||||
{
|
||||
DischargeDateStart = dischargeDate.DateRange.First(),
|
||||
DischargeDateEnd = dischargeDate.DateRange.Last(),
|
||||
FilterChipItems = [dischargeDate]
|
||||
};
|
||||
|
||||
var filterStrings = await ExplorationFilterService.GetFilterStringsAsync(filters.FilterChipItems, queryParams, CancellationToken.None);
|
||||
var ctfSummaryQuery = ExplorationServiceQuery.BuildCaseTypeFamilySummaryQuery(filterStrings.QueryFilters, filterStrings.EncounterSummaryFilters);
|
||||
|
||||
var ctfCostDriverInfos = await SnowflakeDatabaseContext
|
||||
.QueryAsync<CaseTypeFamilyEncounterInfo>(ctfSummaryQuery, "CaseTypeFamily CostDriver Summary", queryParams);
|
||||
Assert.That(ctfCostDriverInfos, Is.Not.Null.And.Not.Empty
|
||||
.And.All.InstanceOf<CaseTypeFamilyEncounterInfo>());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="OpportunityMetricCohortQueryTests\**" />
|
||||
<EmbeddedResource Remove="OpportunityMetricCohortQueryTests\**" />
|
||||
<None Remove="OpportunityMetricCohortQueryTests\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="SnowflakeIntegrationTests\DistributionProcess\TestData\**\*.csv">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.400.36" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="NUnit" Version="4.3.2" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="Strata.ApiLib.Core" Version="8.4.1" />
|
||||
<PackageReference Include="Strata.Configuration.Client" Version="9.15.1" />
|
||||
<PackageReference Include="Strata.SnowflakeLib" Version="4.3.0" />
|
||||
<PackageReference Include="Strata.SqlTools.EntityFramework" Version="6.0.3" />
|
||||
<PackageReference Include="Verify.NUnit" Version="29.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Strata.ContinuousImprovement.Biz\Strata.ContinuousImprovement.Biz.csproj" />
|
||||
<ProjectReference Include="..\Strata.ContinuousImprovement.Biz.Test.Unit\Strata.ContinuousImprovement.Biz.Test.Unit.csproj" />
|
||||
<ProjectReference Include="..\Strata.ContinuousImprovement.JazzEntityFrameworkStub\Strata.ContinuousImprovement.JazzEntityFrameworkStub.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="NUnit.Framework" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="SnowflakeIntegrationTests\DistributionProcess\snapshots\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+533
@@ -0,0 +1,533 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Strata.ContinuousImprovement.Biz.DbContexts;
|
||||
using Strata.ContinuousImprovement.Biz.Generics;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
|
||||
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums;
|
||||
using Strata.ContinuousImprovement.Biz.Utilities;
|
||||
using Strata.Id.Client;
|
||||
using Strata.SqlTools.Configuration.Common.AsyncFactory;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration.StrategicItems
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture]
|
||||
public class StrategicItemDimensionSyncTests : IntegrationTestBase
|
||||
{
|
||||
private StrategicItemDimensionSyncService _syncService;
|
||||
private int _testStrataId;
|
||||
private List<long> _testOpportunityIds;
|
||||
private List<Guid> _testUserGuids;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public async Task TestFixtureSetup()
|
||||
{
|
||||
await SetDbContextAndServices();
|
||||
|
||||
// Get real StrataId from the test database
|
||||
var idServiceClient = ServiceProvider.GetRequiredService<IIdServiceClient>();
|
||||
_testStrataId = await idServiceClient.GetStrataId(GmAutomationDatabase, CancellationToken.None);
|
||||
|
||||
_testOpportunityIds = new List<long>();
|
||||
_testUserGuids = new List<Guid>();
|
||||
|
||||
try
|
||||
{
|
||||
_testUserGuids = await CreateTestUserProfiles();
|
||||
|
||||
_syncService = new StrategicItemDimensionSyncService(
|
||||
ServiceProvider.GetRequiredService<IAsyncDbContextFactory<JazzDbContext>>(),
|
||||
(CentralDbContext)CentralDbContext,
|
||||
SnowflakeDatabaseContext
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If setup fails, clean up any partial data
|
||||
await CleanupTestUserProfiles();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public async Task TestCleanup()
|
||||
{
|
||||
// Clean up Snowflake dimension records
|
||||
foreach (var opportunityId in _testOpportunityIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deleteSql = "DELETE FROM cci.dimstrategicitem WHERE strategic_item_id = :StrategicItemId";
|
||||
await SnowflakeDatabaseContext.ExecuteCommandAsync(deleteSql, new { StrategicItemId = opportunityId });
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up CentralDbContext records
|
||||
var opportunities = await ((CentralDbContext)CentralDbContext).StrategicOpportunities
|
||||
.Where(o => _testOpportunityIds.Contains(o.OpportunityId))
|
||||
.ToListAsync();
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunities.RemoveRange(opportunities);
|
||||
|
||||
var glOpportunities = await ((CentralDbContext)CentralDbContext).StrategicOpportunityGLs
|
||||
.Where(o => _testOpportunityIds.Contains(o.OpportunityId))
|
||||
.ToListAsync();
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunityGLs.RemoveRange(glOpportunities);
|
||||
|
||||
// Clean up stakeholder links
|
||||
var stakeholders = await ((CentralDbContext)CentralDbContext).StakeholdersLinks
|
||||
.Where(s => _testOpportunityIds.Contains(s.StrategicOpportunityId))
|
||||
.ToListAsync();
|
||||
((CentralDbContext)CentralDbContext).StakeholdersLinks.RemoveRange(stakeholders);
|
||||
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
|
||||
_testOpportunityIds.Clear();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public async Task TestFixtureTearDown()
|
||||
{
|
||||
await CleanupTestUserProfiles();
|
||||
|
||||
// Ensure base class cleanup runs to delete CentralDbContext database
|
||||
await base.TearDown();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpsertStrategicItemDimension_NewOpportunity_ShouldInsertDimensionRecord()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTestOpportunity(_testUserGuids);
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunityChargeCodes.Add(opportunity);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
_testOpportunityIds.Add(opportunity.OpportunityId);
|
||||
|
||||
// Act
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var dimensionRecord = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
|
||||
dimensionRecord.Should().NotBeNull();
|
||||
dimensionRecord.Name.Should().Be(opportunity.Name);
|
||||
dimensionRecord.StrategicItemType.Should().Be("Opportunity");
|
||||
dimensionRecord.OpportunityType.Should().Be("Charge Code");
|
||||
dimensionRecord.Status.Should().Be("New Opportunity");
|
||||
dimensionRecord.BaselineType.Should().Be(opportunity.BaselineType.ToString().SplitCamelCase());
|
||||
dimensionRecord.BaselineDistribution.Should().Be(opportunity.BaselineDistribution.ToString());
|
||||
dimensionRecord.RollupOneId.Should().Be(opportunity.Rollup1Id);
|
||||
dimensionRecord.RollupTwoId.Should().Be(opportunity.Rollup2Id);
|
||||
dimensionRecord.RollupThreeId.Should().Be(opportunity.Rollup3Id);
|
||||
dimensionRecord.BaselineStartDate.Should().Be(opportunity.BaselineStartDate);
|
||||
dimensionRecord.BaselineEndDate.Should().Be(opportunity.BaselineEndDate);
|
||||
dimensionRecord.EstimatedTrackingDuration.Should().Be(opportunity.EstimatedTrackingDuration);
|
||||
dimensionRecord.TeamMembers.Should().Be(opportunity.TeamMembers);
|
||||
dimensionRecord.Owner.Should().NotBeNullOrEmpty();
|
||||
dimensionRecord.CostLeader.Should().NotBeNullOrEmpty();
|
||||
dimensionRecord.ExecutiveSponsor.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpsertStrategicItemDimension_ExistingOpportunity_ShouldUpdateDimensionRecord()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTestOpportunity(_testUserGuids);
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunityChargeCodes.Add(opportunity);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
_testOpportunityIds.Add(opportunity.OpportunityId);
|
||||
|
||||
// Insert initial dimension record
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Modify opportunity
|
||||
opportunity.Name = "Updated Test Opportunity";
|
||||
opportunity.Status = (byte)StrategicOpportunityStatus.InValidation;
|
||||
opportunity.EstimatedGoal = 50000;
|
||||
opportunity.TotalImprovement = 25000;
|
||||
opportunity.LastRunTrackingDate = DateTime.SpecifyKind(new DateTime(2024, 3, 15), DateTimeKind.Utc);
|
||||
opportunity.LastRunTrackingStatus = RunTrackingStatus.Success;
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var dimensionRecord = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
|
||||
dimensionRecord.Should().NotBeNull();
|
||||
dimensionRecord.Name.Should().Be("Updated Test Opportunity");
|
||||
dimensionRecord.Status.Should().Be("In Validation");
|
||||
dimensionRecord.EstimatedGoal.Should().Be(50000);
|
||||
dimensionRecord.TotalImprovement.Should().Be(25000);
|
||||
dimensionRecord.LastRunTrackingDate.Should().Be(new DateOnly(2024, 3, 15));
|
||||
dimensionRecord.LastRunTrackingStatus.Should().Be(RunTrackingStatus.Success.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpsertStrategicItemDimension_WithStakeholders_ShouldSyncStakeholderNames()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTestOpportunity(_testUserGuids);
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunityChargeCodes.Add(opportunity);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
_testOpportunityIds.Add(opportunity.OpportunityId);
|
||||
|
||||
// Add stakeholders
|
||||
var stakeholder1 = new StrategicStakeholdersLink(opportunity.OpportunityId, _testUserGuids[3]);
|
||||
var stakeholder2 = new StrategicStakeholdersLink(opportunity.OpportunityId, _testUserGuids[4]);
|
||||
|
||||
((CentralDbContext)CentralDbContext).StakeholdersLinks.AddRange(stakeholder1, stakeholder2);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var dimensionRecord = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
|
||||
dimensionRecord.Should().NotBeNull();
|
||||
dimensionRecord.Stakeholders.Should().NotBeNullOrEmpty();
|
||||
dimensionRecord.Stakeholders.Should().Contain("Stakeholder1, Test");
|
||||
dimensionRecord.Stakeholders.Should().Contain("Stakeholder2, Test");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpsertStrategicItemDimension_StatusChange_ShouldUpdateStrategicItemType()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTestOpportunity(_testUserGuids);
|
||||
opportunity.Status = (byte)StrategicOpportunityStatus.NewOpportunity;
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunityChargeCodes.Add(opportunity);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
_testOpportunityIds.Add(opportunity.OpportunityId);
|
||||
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
var initialRecord = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
initialRecord.StrategicItemType.Should().Be("Opportunity");
|
||||
|
||||
// Change to Initiative
|
||||
opportunity.Status = (byte)StrategicOpportunityStatus.NewInitiative;
|
||||
opportunity.TrackingStartDate = new DateOnly(2024, 1, 1);
|
||||
opportunity.TrackingEndDate = new DateOnly(2024, 12, 31);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var updatedRecord = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
updatedRecord.StrategicItemType.Should().Be("Initiative");
|
||||
updatedRecord.Status.Should().Be("New Initiative");
|
||||
updatedRecord.TrackingStartDate.Should().Be(new DateOnly(2024, 1, 1));
|
||||
updatedRecord.TrackingEndDate.Should().Be(new DateOnly(2024, 12, 31));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpsertStrategicItemDimension_GLOpportunity_ShouldSetOpportunityTypeToGeneralLedger()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTestGLOpportunity(_testUserGuids);
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunityGLs.Add(opportunity);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
_testOpportunityIds.Add(opportunity.OpportunityId);
|
||||
|
||||
// Act
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var dimensionRecord = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
|
||||
dimensionRecord.Should().NotBeNull();
|
||||
dimensionRecord.OpportunityType.Should().Be("General Ledger");
|
||||
dimensionRecord.Name.Should().Be(opportunity.Name);
|
||||
dimensionRecord.StrategicItemType.Should().Be("Opportunity");
|
||||
dimensionRecord.Status.Should().Be("New Opportunity");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpsertStrategicItemDimension_GLOpportunityTrackingStatus_ShouldSyncLastRunTracking()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTestGLOpportunity(_testUserGuids);
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunityGLs.Add(opportunity);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
_testOpportunityIds.Add(opportunity.OpportunityId);
|
||||
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Simulate tracking run completing
|
||||
opportunity.LastRunTrackingDate = DateTime.SpecifyKind(new DateTime(2024, 5, 1), DateTimeKind.Utc);
|
||||
opportunity.LastRunTrackingStatus = RunTrackingStatus.Success;
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var dimensionRecord = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
|
||||
dimensionRecord.Should().NotBeNull();
|
||||
dimensionRecord.LastRunTrackingDate.Should().Be(new DateOnly(2024, 5, 1));
|
||||
dimensionRecord.LastRunTrackingStatus.Should().Be(RunTrackingStatus.Success.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeleteStrategicItemDimension_ShouldRemoveDimensionRecord()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTestOpportunity(_testUserGuids);
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunityChargeCodes.Add(opportunity);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
_testOpportunityIds.Add(opportunity.OpportunityId);
|
||||
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
var recordBeforeDelete = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
recordBeforeDelete.Should().NotBeNull();
|
||||
|
||||
// Act
|
||||
await _syncService.DeleteAsync(opportunity.OpportunityId);
|
||||
|
||||
// Assert
|
||||
var recordAfterDelete = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
recordAfterDelete.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpsertStrategicItemDimension_WithNullDates_ShouldHandleNullsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var opportunity = CreateTestOpportunity(_testUserGuids);
|
||||
opportunity.TrackingStartDate = default;
|
||||
opportunity.TrackingEndDate = default;
|
||||
opportunity.LastRunTrackingDate = null;
|
||||
|
||||
((CentralDbContext)CentralDbContext).StrategicOpportunityChargeCodes.Add(opportunity);
|
||||
await ((CentralDbContext)CentralDbContext).SaveChangesAsync();
|
||||
_testOpportunityIds.Add(opportunity.OpportunityId);
|
||||
|
||||
// Act
|
||||
await _syncService.UpsertAsync(opportunity, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var dimensionRecord = await GetDimensionRecord(opportunity.OpportunityId);
|
||||
|
||||
dimensionRecord.Should().NotBeNull();
|
||||
dimensionRecord.TrackingStartDate.Should().BeNull();
|
||||
dimensionRecord.TrackingEndDate.Should().BeNull();
|
||||
dimensionRecord.LastRunTrackingDate.Should().BeNull();
|
||||
dimensionRecord.BaselineStartDate.Should().NotBeNull(); // Required field
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private async Task CleanupTestUserProfiles()
|
||||
{
|
||||
if (_testUserGuids == null || _testUserGuids.Count == 0) return;
|
||||
|
||||
var jazzContextFactory = ServiceProvider.GetRequiredService<IAsyncDbContextFactory<JazzDbContext>>();
|
||||
await using var jazzContext = await jazzContextFactory.CreateDbContextAsync(CancellationToken.None);
|
||||
|
||||
var guidParams = _testUserGuids
|
||||
.Select((g, i) => new Microsoft.Data.SqlClient.SqlParameter($"@g{i}", g))
|
||||
.ToArray();
|
||||
|
||||
var paramNames = string.Join(", ", guidParams.Select(p => p.ParameterName));
|
||||
await jazzContext.Database.ExecuteSqlRawAsync(
|
||||
$"DELETE FROM dbo.UserProfile WHERE UserGuid IN ({paramNames})",
|
||||
guidParams.Cast<object>().ToArray());
|
||||
}
|
||||
|
||||
private async Task<List<Guid>> CreateTestUserProfiles()
|
||||
{
|
||||
var runId = Guid.NewGuid().ToString("N")[..8];
|
||||
|
||||
var userGuids = new List<Guid>
|
||||
{
|
||||
Guid.NewGuid(), // Owner
|
||||
Guid.NewGuid(), // Cost Leader
|
||||
Guid.NewGuid(), // Executive Sponsor
|
||||
Guid.NewGuid(), // Stakeholder 1
|
||||
Guid.NewGuid() // Stakeholder 2
|
||||
};
|
||||
|
||||
var jazzContextFactory = ServiceProvider.GetRequiredService<IAsyncDbContextFactory<JazzDbContext>>();
|
||||
await using var jazzContext = await jazzContextFactory.CreateDbContextAsync(CancellationToken.None);
|
||||
|
||||
var insertSql = @"
|
||||
INSERT INTO dbo.UserProfile (UserGuid, UserName, NameFirst, NameLast)
|
||||
VALUES (@UserGuid, @UserName, @NameFirst, @NameLast)";
|
||||
|
||||
var profiles = new[]
|
||||
{
|
||||
new { UserGuid = userGuids[0], UserName = $"owner_{runId}", NameFirst = "Test", NameLast = "Owner" },
|
||||
new { UserGuid = userGuids[1], UserName = $"costleader_{runId}", NameFirst = "Test", NameLast = "CostLeader" },
|
||||
new { UserGuid = userGuids[2], UserName = $"executivesponsor_{runId}", NameFirst = "Test", NameLast = "ExecutiveSponsor" },
|
||||
new { UserGuid = userGuids[3], UserName = $"stakeholder1_{runId}", NameFirst = "Test", NameLast = "Stakeholder1" },
|
||||
new { UserGuid = userGuids[4], UserName = $"stakeholder2_{runId}", NameFirst = "Test", NameLast = "Stakeholder2" }
|
||||
};
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
await jazzContext.Database.ExecuteSqlRawAsync(insertSql,
|
||||
new Microsoft.Data.SqlClient.SqlParameter("@UserGuid", profile.UserGuid),
|
||||
new Microsoft.Data.SqlClient.SqlParameter("@UserName", profile.UserName),
|
||||
new Microsoft.Data.SqlClient.SqlParameter("@NameFirst", profile.NameFirst),
|
||||
new Microsoft.Data.SqlClient.SqlParameter("@NameLast", profile.NameLast));
|
||||
}
|
||||
|
||||
return userGuids;
|
||||
}
|
||||
|
||||
private StrategicOpportunityChargeCode CreateTestOpportunity(List<Guid> userGuids)
|
||||
{
|
||||
return new StrategicOpportunityChargeCode
|
||||
{
|
||||
Name = $"Test Opportunity {Guid.NewGuid()}",
|
||||
StrataId = _testStrataId,
|
||||
OpportunityType = StrategicOpportunityTypes.ChargeCode,
|
||||
Status = (byte)StrategicOpportunityStatus.NewOpportunity,
|
||||
BaselineType = BaselineType.MonthRange,
|
||||
BaselineDistribution = DistributionMethod.Monthly,
|
||||
BaselineStartDate = new DateOnly(2024, 1, 1),
|
||||
BaselineEndDate = new DateOnly(2024, 6, 30),
|
||||
EstimatedTrackingDuration = 12,
|
||||
EstimatedGoal = 100000,
|
||||
TotalImprovement = 0,
|
||||
Rollup1Id = 1,
|
||||
Rollup2Id = 1,
|
||||
Rollup3Id = 1,
|
||||
OwnerGuid = userGuids[0],
|
||||
CostLeaderGuid = userGuids[1],
|
||||
ExecutiveSponsorGuid = userGuids[2],
|
||||
TeamMembers = "Team Member 1, Team Member 2",
|
||||
ChargeCodeHPath = "test/path",
|
||||
PatientPopulationHPath = string.Empty,
|
||||
OpportunityFiltersJSON = "[]",
|
||||
LayoutJSON = "{}",
|
||||
LastRunTrackingStatus = RunTrackingStatus.None
|
||||
};
|
||||
}
|
||||
|
||||
private StrategicOpportunityGL CreateTestGLOpportunity(List<Guid> userGuids)
|
||||
{
|
||||
return new StrategicOpportunityGL
|
||||
{
|
||||
Name = $"Test GL Opportunity {Guid.NewGuid()}",
|
||||
StrataId = _testStrataId,
|
||||
OpportunityType = StrategicOpportunityTypes.GeneralLedger,
|
||||
Status = (byte)StrategicOpportunityStatus.NewOpportunity,
|
||||
BaselineType = BaselineType.MonthRange,
|
||||
BaselineDistribution = DistributionMethod.Monthly,
|
||||
BaselineStartDate = new DateOnly(2024, 1, 1),
|
||||
BaselineEndDate = new DateOnly(2024, 6, 30),
|
||||
EstimatedTrackingDuration = 12,
|
||||
EstimatedGoal = 100000,
|
||||
TotalImprovement = 0,
|
||||
Rollup1Id = 1,
|
||||
Rollup2Id = 1,
|
||||
Rollup3Id = 1,
|
||||
OwnerGuid = userGuids[0],
|
||||
CostLeaderGuid = userGuids[1],
|
||||
ExecutiveSponsorGuid = userGuids[2],
|
||||
TeamMembers = "Team Member 1, Team Member 2",
|
||||
DepartmentHPath = string.Empty,
|
||||
OpportunityFiltersJSON = "[]",
|
||||
LayoutJSON = "{}",
|
||||
LastRunTrackingStatus = RunTrackingStatus.None
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<DimensionRecord> GetDimensionRecord(long opportunityId)
|
||||
{
|
||||
var query = @"
|
||||
SELECT
|
||||
strategic_item_id AS StrategicItemId,
|
||||
name AS Name,
|
||||
rollup_one_id AS RollupOneId,
|
||||
rollup_two_id AS RollupTwoId,
|
||||
rollup_three_id AS RollupThreeId,
|
||||
baseline_type AS BaselineType,
|
||||
strategic_item_type AS StrategicItemType,
|
||||
opportunity_type AS OpportunityType,
|
||||
status AS Status,
|
||||
baseline_distribution AS BaselineDistribution,
|
||||
baseline_start_date AS BaselineStartDate,
|
||||
baseline_end_date AS BaselineEndDate,
|
||||
estimated_tracking_duration AS EstimatedTrackingDuration,
|
||||
tracking_start_date AS TrackingStartDate,
|
||||
tracking_end_date AS TrackingEndDate,
|
||||
estimated_goal AS EstimatedGoal,
|
||||
total_improvement AS TotalImprovement,
|
||||
cost_leader AS CostLeader,
|
||||
executive_sponsor AS ExecutiveSponsor,
|
||||
owner AS Owner,
|
||||
stakeholders AS Stakeholders,
|
||||
team_members AS TeamMembers,
|
||||
last_run_tracking_date AS LastRunTrackingDate,
|
||||
last_run_tracking_status AS LastRunTrackingStatus
|
||||
FROM cci.dimstrategicitem
|
||||
WHERE strategic_item_id = :StrategicItemId";
|
||||
|
||||
var parameters = new { StrategicItemId = opportunityId };
|
||||
|
||||
var results = await SnowflakeDatabaseContext.QueryAsync<DimensionRecord>(
|
||||
query,
|
||||
"GetDimensionRecord",
|
||||
parameters
|
||||
);
|
||||
|
||||
return results.FirstOrDefault();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test DTO
|
||||
|
||||
private class DimensionRecord
|
||||
{
|
||||
public long StrategicItemId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int RollupOneId { get; set; }
|
||||
public int RollupTwoId { get; set; }
|
||||
public int RollupThreeId { get; set; }
|
||||
public string BaselineType { get; set; }
|
||||
public string StrategicItemType { get; set; }
|
||||
public string OpportunityType { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string BaselineDistribution { get; set; }
|
||||
public DateOnly? BaselineStartDate { get; set; }
|
||||
public DateOnly? BaselineEndDate { get; set; }
|
||||
public int EstimatedTrackingDuration { get; set; }
|
||||
public DateOnly? TrackingStartDate { get; set; }
|
||||
public DateOnly? TrackingEndDate { get; set; }
|
||||
public decimal EstimatedGoal { get; set; }
|
||||
public decimal TotalImprovement { get; set; }
|
||||
public string CostLeader { get; set; }
|
||||
public string ExecutiveSponsor { get; set; }
|
||||
public string Owner { get; set; }
|
||||
public string Stakeholders { get; set; }
|
||||
public string TeamMembers { get; set; }
|
||||
public DateOnly? LastRunTrackingDate { get; set; }
|
||||
public string LastRunTrackingStatus { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Strata.ContinuousImprovement.Biz.Test.Integration
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
[TestFixture, Category("Integration"), Category("CCI")]
|
||||
public class TestCIIntegration : IntegrationTestBase
|
||||
{
|
||||
[Test]
|
||||
public async Task VariationOpportunityTest()
|
||||
{
|
||||
var query = @"select OpportunityGuid from cci.VariationOpportunity where CostDriver = 'Pharmacy' order by Savings DESC limit 1";
|
||||
var opportunityGuid = await SnowflakeDatabaseContext.ExecuteScalarAsync<string>(query);
|
||||
VariationOpportunityGuid = new Guid(opportunityGuid ?? Guid.NewGuid().ToString());
|
||||
Assert.That(opportunityGuid, Is.Not.Null.And.EqualTo(VariationOpportunityGuid.ToString()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LosVariationOpportunityTest()
|
||||
{
|
||||
var query = @"select OpportunityGuid from cci.VariationOpportunity where CostDriver = 'LOS' order by Savings DESC limit 1";
|
||||
var opportunityGuid = await SnowflakeDatabaseContext.ExecuteScalarAsync<string>(query);
|
||||
LOSOpportunityGuid = new Guid(opportunityGuid ?? Guid.NewGuid().ToString());
|
||||
Assert.That(opportunityGuid, Is.Not.Null.And.EqualTo(LOSOpportunityGuid.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"AllowedHosts": "*",
|
||||
"Version": "0.0.0",
|
||||
"configuration": {
|
||||
"basicAuthUsername": "BkQsRDdmdjkFNhKVuTI.TpujQJXuKhGDcV.F_hjADMRTaEUWMJnviaesoOrhhfnz",
|
||||
"basicAuthPassword": "GWPIIWECDqh.hjDNdpxVEfeFe-bYFwPSx-oBGFD_od_SUQVk-QqTIkZY_NXMeqeo"
|
||||
},
|
||||
"endPoints": {
|
||||
"dataschema": "https://dataschema-api.dev.stratanetwork.net/",
|
||||
"schema": "https://schema-api.dev.stratanetwork.net",
|
||||
"analytics": "https://analytics-api.dev.stratanetwork.net"
|
||||
},
|
||||
"StrataConfigServerBaseUrl": "https://configuration.dev.stratanetwork.net",
|
||||
"NetworkOpportunity": {
|
||||
"Creators": [ "" ]
|
||||
},
|
||||
"HealthCheck": {
|
||||
"StrataJazz": true,
|
||||
"Snowflake": true
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"hangfire": {
|
||||
"url": "https://localhost:44369",
|
||||
"redis": {
|
||||
"prefix": "continuousimprovement-jobs-service",
|
||||
"configOptions": "ddenredisc01.sdt.local:6379,ddenredisc02.sdt.local:6379,ddenredisc03.sdt.local:6379,ddenredisc04.sdt.local:6379,ddenredisc05.sdt.local:6379,ddenredisc06.sdt.local:6379"
|
||||
}
|
||||
},
|
||||
"accessGudid": {
|
||||
"url": "https://accessgudid.nlm.nih.gov/download/delimited"
|
||||
},
|
||||
"datadog": {
|
||||
"seedgmdnjob": "https://app.datadoghq.com/logs/livetail?query=service%3ASeedGMDNTermDataJob%20env%3Adev&cols=status%2Cenv%2C%40db.strata_id%2C%40db.org_pin%2C%40db.name%2C%40db.instance%2C%40process.name%2Cservice&index=%2A&messageDisplay=inline&stream_sort=desc&viz=stream&from_ts=1692207497147&to_ts=1692293897147&live=true"
|
||||
},
|
||||
"aws": {
|
||||
"snowflakeAdminSecretName": "stratareplication/snowflake/automation/connectionstring"
|
||||
},
|
||||
"strataApi": {
|
||||
"clientSecret": "V_okTrkWYBs-PqMP.inbJShIvMGWcpdqCDq_ZjntVF.ydgRCoYaDVJYEWEIdsjJ_"
|
||||
},
|
||||
"ChargeCodeOpportunityValidation": {
|
||||
"maxChargeCodeSelections": 100,
|
||||
"maxOpportunityLevelSelections": 10
|
||||
},
|
||||
"Snowflake": {
|
||||
"Url": "https://stratadev.us-east-1.privatelink.snowflakecomputing.com",
|
||||
"Account": "stratadev"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user