chore: deploy initial code base

This commit is contained in:
Thom Lamb
2026-05-12 10:34:30 -05:00
parent cce2961782
commit a81300a571
1829 changed files with 248331 additions and 1 deletions
@@ -0,0 +1,7 @@
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
internal class AddressInfo
{
public string BaseAddress { get; set; }
}
}
@@ -0,0 +1,23 @@
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class AllOpportunityInfo
{
public Guid ConfigurationGuid { get; set; }
public string CostDriver { get; set; }
public bool HasInitiative { get; set; }
public double IdentifiedSavings { get; set; }
public string Initiative { get; set; }
public Guid InitiativeGuid { get; set; }
public bool IsHidden { get; set; }
public string Name { get; set; }
public string Note { get; set; }
public Guid OpportunityGuid { get; set; }
public long OpportunityId { get; set; }
public string OpportunityKey { get; set; }
public int OpportunityType { get; set; }
public string OpportunityTypeName { get; set; }
}
}
@@ -0,0 +1,10 @@
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class CaseTypeFamilyInfo
{
public int CaseTypeFamilyId { get; set; }
public Guid CaseTypeFamilyGuid { get; set; }
public string Name { get; set; }
public string CaseTypeFamilyGlobalId { get; set; }
}
}
@@ -0,0 +1,56 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
[ExcludeFromCodeCoverage]
public class ConfigurationInfo
{
public Guid ConfigurationGuid { get; set; }
public string Name { get; set; }
public byte DisplayOrder { get; set; }
public Guid ServiceLineDimensionGuid { get; set; }
[JsonPropertyName("entityIdsCSV")]
public string EntityIdsCsv { get; set; }
public bool IsUsingIndirectCost { get; set; }
public string DepartmentRollupGlobalId { get; set; }
public bool IsUsingCustomEntityDimension => !string.IsNullOrEmpty(DepartmentRollupGlobalId);
[JsonPropertyName("customEntityIdsCSV")]
public string CustomEntityIdsCsv { get; set; }
[JsonPropertyName("fiscalYearID")]
public short FiscalYearId { get; set; }
public DateTime OrgDefinedEndDate { get; set; }
public DateTime OrgDefinedTrackingCurrentMonth { get; set; }
}
[ExcludeFromCodeCoverage]
public class EntityIdInfo
{
[JsonPropertyName("id")]
public int EntityId { get; set; }
[JsonPropertyName("name")]
public string EntityName { get; set; } = string.Empty;
}
[ExcludeFromCodeCoverage]
public class EntityInfo
{
public string EntityTitle { get; set; }
public string EntityLabel { get; set; }
public string EntityText { get; set; }
public string DepartmentRollupGlobalId { get; set; }
public string HierarchicalGuid { get; set; }
}
[ExcludeFromCodeCoverage]
public class ServiceLineInfo
{
public string ServiceLineGlobalId { get; set; }
public string ServiceLineTitle { get; set; }
public string ServiceLineName { get; set; }
public string ServiceLineDimensionGuid { get; set; }
public string ServiceLineHierarchicalGuid { get; set; }
}
}
@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class CostDriver
{
[JsonPropertyName("name")]
public string Name { get; set; }
}
}
@@ -0,0 +1,25 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class InitiativeDefaultColumnInfo
{
[JsonPropertyName("index")]
public int Index { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("heading")]
public string Heading { get; set; }
[JsonPropertyName("isLocked")]
public bool IsLocked { get; set; }
[JsonPropertyName("isHidden")]
public bool IsHidden { get; set; }
[JsonPropertyName("initiativeRollupColumnId")]
public int? InitiativeRollupColumnId { get; set; }
}
}
@@ -0,0 +1,25 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class InitiativeDetailColumnInfo
{
[JsonPropertyName("index")]
public int Index { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("heading")]
public string Heading { get; set; }
[JsonPropertyName("isLocked")]
public bool IsLocked { get; set; }
[JsonPropertyName("isHidden")]
public bool IsHidden { get; set; }
[JsonPropertyName("initiativeRollupColumnId")]
public int? InitiativeRollupColumnId { get; set; }
}
}
@@ -0,0 +1,31 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class InitiativeFilterOptionsInfo
{
[JsonPropertyName("costDrivers")]
public List<CostDriver> CostDrivers { get; set; }
[JsonPropertyName("costLeaders")]
public List<CostLeader> CostLeaders { get; set; }
[JsonPropertyName("owners")]
public List<Owner> Owners { get; set; }
[JsonPropertyName("executiveSponsors")]
public List<ExecutiveSponsor> ExecutiveSponsors { get; set; }
[JsonPropertyName("workstreams")]
public List<Workstream> Workstreams { get; set; }
[JsonPropertyName("rollupColumn1Values")]
public object RollupColumn1Values { get; set; }
[JsonPropertyName("rollupColumn2Values")]
public object RollupColumn2Values { get; set; }
[JsonPropertyName("rollupColumn3Values")]
public object RollupColumn3Values { get; set; }
}
}
@@ -0,0 +1,43 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class InitiativeFilterSettingsInfo
{
[JsonPropertyName("configurationGuid")]
public string ConfigurationGuid { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("status")]
public int Status { get; set; }
[JsonPropertyName("costDrivers")]
public List<string> CostDrivers { get; set; }
[JsonPropertyName("costLeaders")]
public List<string> CostLeaders { get; set; }
[JsonPropertyName("owners")]
public List<string> Owners { get; set; }
[JsonPropertyName("executiveSponsors")]
public List<string> ExecutiveSponsors { get; set; }
[JsonPropertyName("workstreams")]
public List<string> Workstreams { get; set; }
[JsonPropertyName("rollupColumn1Values")]
public List<string> RollupColumn1Values { get; set; }
[JsonPropertyName("rollupColumn2Values")]
public List<string> RollupColumn2Values { get; set; }
[JsonPropertyName("rollupColumn3Values")]
public List<string> RollupColumn3Values { get; set; }
[JsonPropertyName("groupByCategory")]
public string GroupByCategory { get; set; }
}
}
@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class InitiativeRollupColumnInfo
{
[JsonPropertyName("initiativeRollupColumnId")]
public int InitiativeRollupColumnId { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("initiativeRollupValues")]
public List<InitiativeRollupValue> InitiativeRollupValues { get; set; }
}
}
@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class InitiativeRollupValue
{
[JsonPropertyName("initiativeRollupValueId")]
public int InitiativeRollupValueId { get; set; }
[JsonPropertyName("initiativeRollupColumnId")]
public int InitiativeRollupColumnId { get; set; }
[JsonPropertyName("value")]
public string Value { get; set; }
}
}
@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class SecurityInfo()
{
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
}
}
@@ -0,0 +1,26 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class User
{
[JsonPropertyName("userGuid")]
public string UserGuid { get; set; }
[JsonPropertyName("nameFull")]
public string NameFull { get; set; }
}
public class CostLeader : User
{
}
public class ExecutiveSponsor : User
{
}
public class Owner : User
{
}
}
@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Info
{
public class Workstream
{
[JsonPropertyName("name")]
public string Name { get; set; }
}
}
@@ -0,0 +1,86 @@
using Riok.Mapperly.Abstractions;
using Strata.ContinuousImprovement.Biz.Dtos;
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities;
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters;
using Strata.ContinuousImprovement.Biz.OrgDefined.Queries;
using Strata.ContinuousImprovement.Biz.Shared;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Mapperly
{
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)]
public static partial class OpportunityMetricBaseMap
{
public static OpportunityMetricBase Map(this ENOpportunityDataModel dataModel)
{
var enOpportunityBase = JsonSerializer.Deserialize<ENOpportunityBaseFilters>(dataModel.EncounterOpportunityJSON);
OpportunityMetricBase target = new OpportunityMetricBase();
if (enOpportunityBase != null)
{
target = enOpportunityBase.Map();
target.CaseTypeIds = enOpportunityBase
.CaseTypes.Select(fh => int.TryParse(fh.Id, out var id) ? id : -1).Where(id => id >= 0).ToList();
switch (enOpportunityBase.EncounterOpportunityLevel)
{
case OpportunityLevel.Entity:
target.OpportunityLevelIds = enOpportunityBase
.Entities.Select(fh => int.TryParse(fh.Id, out var id) ? id : -1).Where(id => id >= 0).ToList();
break;
case OpportunityLevel.ServiceLine:
target.OpportunityLevelIds = enOpportunityBase
.ServiceLines.Select(fh => int.TryParse(fh.Id, out var id) ? id : -1).Where(id => id >= 0).ToList();
break;
case OpportunityLevel.Specialty:
target.OpportunityLevelIds = enOpportunityBase
.Specialties.Select(fh => int.TryParse(fh.Id, out var id) ? id : -1).Where(id => id >= 0).ToList();
break;
case OpportunityLevel.Provider:
target.OpportunityLevelIds = enOpportunityBase
.Providers.Select(fh => int.TryParse(fh.Id, out var id) ? id : -1).Where(id => id >= 0).ToList();
break;
}
}
return target;
}
public static OpportunityMetricBase Map(this ENOpportunityDto source)
{
var target = source.MapTo();
var enOpportunityBase = JsonSerializer.Deserialize<ENOpportunityBaseFilters>(source.EncounterOpportunityJSON);
var regexp = new Regex(@"[^\d]*\|([\d]+)", RegexOptions.Compiled);
if (enOpportunityBase != null)
{
target = enOpportunityBase.Map();
target.CaseTypeIds = enOpportunityBase
.CaseTypes.Select(fh => int.TryParse(regexp.Replace(fh.Id, "$1"), out var id) ? id : -1).Where(id => id >= 0).ToList();
switch (enOpportunityBase.EncounterOpportunityLevel)
{
case OpportunityLevel.Entity:
target.OpportunityLevelIds = enOpportunityBase
.Entities.Select(fh => int.TryParse(regexp.Replace(fh.Id, "$1"), out var id) ? id : -1).Where(id => id >= 0).ToList();
break;
case OpportunityLevel.ServiceLine:
target.OpportunityLevelIds = enOpportunityBase
.ServiceLines.Select(fh => int.TryParse(regexp.Replace(fh.Id, "$1"), out var id) ? id : -1).Where(id => id >= 0).ToList();
break;
case OpportunityLevel.Specialty:
target.OpportunityLevelIds = enOpportunityBase
.Specialties.Select(fh => int.TryParse(regexp.Replace(fh.Id, "$1"), out var id) ? id : -1).Where(id => id >= 0).ToList();
break;
case OpportunityLevel.Provider:
target.OpportunityLevelIds = enOpportunityBase
.Providers.Select(fh => int.TryParse(regexp.Replace(fh.Id, "$1"), out var id) ? id : -1).Where(id => id >= 0).ToList();
break;
}
target.OpportunityLevelIdAsCSV = string.Join(",", target.OpportunityLevelIds);
}
return target;
}
public static partial OpportunityMetricBase Map(this ENOpportunityBaseFilters enOpportunityBase);
[MapperIgnoreSource(nameof(ENOpportunityDto.OpportunityLevelIds))]
private static partial OpportunityMetricBase MapTo(this ENOpportunityDto source);
}
}
@@ -0,0 +1,38 @@
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Api.Test.Integration.Setup;
using Strata.ContinuousImprovement.Api.Test.Integration.Tests;
using System.Diagnostics.CodeAnalysis;
[ExcludeFromCodeCoverage]
[SetUpFixture]
public class ClientIntegrationTestFixture
{
[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 clientService = new ApiTestBase();
await clientService.ClientValidation();
var services = ServiceCollectionFactory.Create();
await services.SetupServiceProperties(clientService.StrataId);
}
[OneTimeTearDown]
public async Task TearDown()
{
try
{
var centralDbContext = ServiceProperties.CentralDbContext as CentralDbContext;
if (centralDbContext == null) { return; }
_ = await centralDbContext.Database.EnsureDeletedAsync(CancellationToken.None);
}
catch (Exception ex)
{
throw;
}
}
}
@@ -0,0 +1,46 @@
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 System.Diagnostics.CodeAnalysis;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Setup
{
[ExcludeFromCodeCoverage]
public static class ServiceCollectionFactory
{
// dev master
private static readonly Guid DatabaseGuid = new("54409f55-5ddf-4748-8222-9ccfb04fb4f5");
private static readonly string IntegrationTesting = $"ci_api_{DateTime.Now:yyyyMMdd}_{Guid.NewGuid():N}";
public static IServiceCollection Create()
{
var services = new ServiceCollection();
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(DatabaseGuid, token))
.WithDbContextOptions((connectionString, provider, builder) =>
builder.UseSqlServer(connectionString, options => { options.CommandTimeout(300); }));
});
services.AddLogging();
}
catch (Exception)
{
throw;
}
return services;
}
}
}
@@ -0,0 +1,165 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using Strata.Analytics.Client;
using Strata.Analytics.Models.Workbooks;
using Strata.ContinuousImprovement.Api.Test.Integration.Info;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.Generics;
using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks;
using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Setup
{
public static class ServiceProperties
{
// dev master
internal static readonly Guid DatabaseGuid = new("54409f55-5ddf-4748-8222-9ccfb04fb4f5");
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
public static ICentralDbContext CentralDbContext { get; set; }
public static JazzEntityFrameworkFactory JazzConnBuilderFactory { get; set; }
public static IJazzDbContext JazzDbContext { get; set; }
public static Biz.DbContexts.Snowflake.ISnowflakeDatabaseContext SnowflakeDatabaseContext { get; set; }
public static IServiceCollection Services { get; set; }
public static IServiceProvider ServiceProvider { get; set; }
public static IAnalyticsService AnalyticsService { get; set; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
public static string BaseAddress { get; set; } = string.Empty;
private static int WorkbookId = 9;
public static async Task SetupServiceProperties(this IServiceCollection services, int strataId = 0)
{
#if DEBUG
Environment.SetEnvironmentVariable("AWS_Profile", "sdt-continuous-improvement-service-role");
#endif
services.AddOptions<AddressInfo>().BindConfiguration("address").ValidateDataAnnotations(); //bind the options for aws and validates for missing values
Services = services;
var serviceProvider = services.BuildServiceProvider();
ServiceProvider = serviceProvider;
try
{
var sfConnBuilderFactory = serviceProvider.GetRequiredService<Biz.DbContexts.Snowflake.ISnowflakeDatabaseContextFactory>();
SnowflakeDatabaseContext = sfConnBuilderFactory.Create($"{DatabaseGuid:N}");
}
catch (Exception)
{
// ILB
}
var sectionAccessor = serviceProvider.GetRequiredService<IOptions<AddressInfo>>();
var addressInfo = sectionAccessor.Value;
if (addressInfo != null && !string.IsNullOrEmpty(addressInfo.BaseAddress))
{
BaseAddress = addressInfo.BaseAddress;
}
try
{
var jazzConnBuilderFactory = serviceProvider.GetRequiredService<IAsyncDbContextFactory<JazzDbContext>>();
JazzDbContext = await jazzConnBuilderFactory.CreateDbContextAsync(CancellationToken.None);
}
catch (Exception)
{
// ILB
}
await BuildPostgresDb(strataId);
BuildAnalyticsService(strataId);
}
private static async Task BuildPostgresDb(int strataId)
{
try
{
CentralDbContext = ServiceProvider.GetService<CentralDbContext>() as CentralDbContext
?? throw new NullReferenceException(nameof(CentralDbContext));
var centralDbContext = CentralDbContext as CentralDbContext;
if (centralDbContext == null) { throw new NullReferenceException(nameof(centralDbContext)); }
await centralDbContext.Database.MigrateAsync(CancellationToken.None);
Assert.That(centralDbContext, Is.Not.Null);
JazzConnBuilderFactory = new JazzEntityFrameworkFactory();
await JazzConnBuilderFactory.EnsureCreatedAsync(centralDbContext, JazzDbContext, CancellationToken.None);
var workbook = new OpportunityWorkbook
{
StrataId = strataId,
WorkbookId = WorkbookId,
OpportunityGuid = Guid.NewGuid(),
Name = "Test",
};
if (!centralDbContext.OpportunityWorkbooks.Any(ow => ow.StrataId == workbook.StrataId
&& ow.WorkbookId == workbook.WorkbookId))
{
await centralDbContext.OpportunityWorkbooks.AddAsync(workbook, CancellationToken.None);
}
var oppTypeWorkbook = new OpportunityTypeWorkbook
{
StrataId = strataId,
WorkbookId = WorkbookId,
OpportunityType = (int)OpportunityTypes.ChargeCode,
Name = "Test",
};
if (!centralDbContext.OpportunityTypeWorkbooks.Any(otw => otw.StrataId == oppTypeWorkbook.StrataId
&& otw.WorkbookId == oppTypeWorkbook.WorkbookId))
{
await centralDbContext.OpportunityTypeWorkbooks.AddAsync(oppTypeWorkbook, CancellationToken.None);
}
await centralDbContext.SaveChangesAsync(CancellationToken.None);
}
catch (Exception)
{
throw;
}
}
private static void BuildAnalyticsService(int strataId)
{
try
{
var analyticsService = Mock.Of<IAnalyticsService>();
var workbook = new Workbook
{
Name = "Test",
WorkbookId = WorkbookId,
IsHidden = false,
StrataId = strataId,
};
var workbooks = new List<Workbook> { workbook };
workbooks.AddRange(Enumerable.Range(1, 1000).Where(i => i != WorkbookId).Select(i => new Workbook
{
Name = $"Test {i}",
WorkbookId = i,
IsHidden = (i % 2 == 0),
StrataId = (i % 3 == 0) ? 9 : strataId,
}));
//GetWorkbooksAsync
Mock.Get(analyticsService)
.Setup(s => s.GetWorkbookAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
.Returns<int, CancellationToken>((id, token)
=> Task.FromResult(id == WorkbookId ? workbook : null));
Mock.Get(analyticsService)
.Setup(s => s.GetWorkbooksAsync(It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.Returns<bool, CancellationToken>((isHidden, token)
=> Task.FromResult(workbooks.Where(w => w.IsHidden == isHidden)));
AnalyticsService = analyticsService;
}
catch (Exception)
{
throw;
}
}
}
}
@@ -0,0 +1,9 @@
namespace Strata.ContinuousImprovement.Api.Test.Integration.Setup
{
public class VaultResponse
{
public string UserName { get; set; }
public string Password { get; set; }
}
}
@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<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.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.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.Api\Strata.ContinuousImprovement.Api.csproj" />
<ProjectReference Include="..\..\src\Strata.ContinuousImprovement.Biz\Strata.ContinuousImprovement.Biz.csproj" />
<ProjectReference Include="..\Strata.ContinuousImprovement.JazzEntityFrameworkStub\Strata.ContinuousImprovement.JazzEntityFrameworkStub.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
</Project>
@@ -0,0 +1,222 @@
using Microsoft.AspNetCore.Http;
using Moq;
using Strata.ContinuousImprovement.Api.Test.Integration.Setup;
using Strata.ContinuousImprovement.Biz.Configuration;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks;
using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks;
using Strata.CoreLib.Claims;
using Strata.CoreLib.Claims.Extensions;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Security.Claims;
using System.Text.Json;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
public class ApiTestBase
{
protected StrataTokenInfo StrataTokenInfo { get; set; }
protected string AccessToken { get; set; }
protected IEnumerable<Claim> Claims { get; set; }
protected long Expires_in { get; set; }
protected ClaimsIdentity ClaimsIdentity { get; set; }
protected ClaimsPrincipal ClaimsPrincipal { get; set; }
protected Dictionary<string, ClaimsPrincipal> ClaimsPrincipals { get; set; } = new Dictionary<string, ClaimsPrincipal>();
internal IClaimsPrincipalAccessor ClaimsPrincipalAccessor { get; set; }
internal int StrataId { get; set; }
internal Guid DatabaseGuid { get; set; }
protected readonly string BaseAddress = "https://continuousimprovement-api.dev.stratanetwork.net";
public IConfigurationService ConfigurationService { get; set; }
public CentralDbContext CentralDbContext { get; set; }
public IOpportunityWorkbookService OpportunityWorkbookService { get; set; }
#pragma warning disable NUnit1032 // An IDisposable field/property should be Disposed in a TearDown method
public IServiceProvider ServiceProvider { get; set; }
#pragma warning restore NUnit1032 // An IDisposable field/property should be Disposed in a TearDown method
public OpportunityWorkbook OpportunityWorkbook { get; set; }
public OpportunityTypeWorkbook OpportunityTypeWorkbook { get; set; }
[OneTimeSetUp]
public async Task TestSetup()
{
#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 strataBearer = await StrataValidation();
await ClientValidation();
var services = ServiceCollectionFactory.Create();
await services.SetupServiceProperties(StrataId);
ServiceProvider = ServiceProperties.ServiceProvider;
var httpClient = new HttpClient
{
BaseAddress = new Uri(BaseAddress)
};
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
CentralDbContext = (CentralDbContext)ServiceProperties.CentralDbContext;
}
protected HttpClient HttpClient;
[SetUp]
public void SetupHttpClient()
{
HttpClient = new HttpClient
{
BaseAddress = new Uri(BaseAddress)
};
HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
}
[TearDown]
public void TearDownHttpClient()
{
HttpClient.Dispose();
}
#region Client Claims Validation
public async Task<string> ClientValidation()
{
var userInfo = new VaultUserInfo
{
vaultTitle = "ccicostleader", // vault ccicostleader is same as jazz ccicostleader
vaultUserName = "ccicostleader",
userName = "ccicostleader",
fullName = "Administrator, CCI"
};
return await ClientValidation(userInfo);
}
public async Task<string> StrataValidation()
{
var userInfo = new VaultUserInfo
{
vaultTitle = "IDTestUser", // vault ccicostleader is same as jazz ccicostleader
vaultUserName = "sdt\\IDTestUser", // vault ccicostleader is same as jazz ccicostleader
userName = "sdt\\IDTestUser",
fullName = "Administrator, CCI"
};
return await ClientValidation(userInfo);
}
public async Task<string> ClientValidation(VaultUserInfo userInfo)
{
var vaultInfo = await GetUserPassword(userInfo.vaultTitle);
var bearerToken = await GetBearerToken(vaultInfo.Item1, vaultInfo.Item2);
ClaimsIdentity = new ClaimsIdentity(Claims);
ClaimsPrincipal = new ClaimsPrincipal(ClaimsIdentity);
ClaimsPrincipals.Add(userInfo.vaultTitle, ClaimsPrincipal);
StrataId = ClaimsPrincipal.GetStrataId();
DatabaseGuid = ClaimsPrincipal.GetDatabaseGuid();
BuildClaimsPrincipalAccessor();
return bearerToken;
}
internal async Task<Tuple<string, string>> GetUserPassword(string vaultTitle)
{
var client = new HttpClient
{
BaseAddress = new Uri("https://vault.sdt.local/")
};
client.DefaultRequestHeaders.Add("apiKey", "a69d2e478c3f5b395b687ecdf34a3239");
client.DefaultRequestHeaders.Add("ignoreHTTPSErrors", "true");
var vaultResponse = await client.GetAsync($"api/searchpasswords/122?title={vaultTitle}");
vaultResponse.EnsureSuccessStatusCode();
string responseString = await vaultResponse.Content.ReadAsStringAsync();
if (string.IsNullOrEmpty(responseString)) { return new Tuple<string, string>(string.Empty, string.Empty); }
var jsonResponse = JsonSerializer.Deserialize<IEnumerable<VaultResponse>>(responseString);
if (jsonResponse == null) { return new Tuple<string, string>(string.Empty, string.Empty); }
var vaultUserInfo = jsonResponse.First();
client.Dispose();
return new Tuple<string, string>(vaultUserInfo.UserName, vaultUserInfo.Password);
}
internal async Task<string> GetBearerToken(string userName, string password)
{
var form = BuildTokenRequest(userName, password);
var client = new HttpClient();
var response = await client.PostAsync("https://identity.dev.stratanetwork.net/connect/token", form);
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
if (string.IsNullOrEmpty(responseString)) { return string.Empty; }
var strataTokenInfo = JsonSerializer.Deserialize<StrataTokenInfo>(responseString);
if (strataTokenInfo == null) { return string.Empty; }
StrataTokenInfo = strataTokenInfo;
AccessToken = strataTokenInfo.AccessToken;
Expires_in = strataTokenInfo.ExpiresIn;
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(AccessToken);
Claims = jwtToken.Claims;
client.Dispose();
return AccessToken;
}
internal FormUrlEncodedContent BuildTokenRequest(string userName, string password)
{
var form = new FormUrlEncodedContent(
[
new KeyValuePair<string, string>("database_guid", ServiceProperties.DatabaseGuid.ToString()),
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", userName),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>("scope", "strata.api.external offline_access"),
new KeyValuePair<string, string>("client_id", "IntegrationTest"),
new KeyValuePair<string, string>("client_secret", "UhAEQsGByTCrwPdHPLmaet_DmLEfcJVbZqGBxArdeRup_TPLrYKNpUNRHejjdnhd")
]);
return form;
}
internal void BuildClaimsPrincipalAccessor()
{
try
{
var claimsAccessor = Mock.Of<IClaimsPrincipalAccessor>();
Mock.Get(claimsAccessor)
.Setup(s => s.GetCurrentClaimsPrincipal())
.Returns(ClaimsPrincipal);
ClaimsPrincipalAccessor = claimsAccessor;
}
catch (Exception)
{
throw;
}
}
#endregion
#region Helper Methods
protected async Task<IEnumerable<Info.ConfigurationInfo>> GetConfigurations()
{
string ConfigurationsEndpoint = "api/v1/configurations";
var request = new HttpRequestMessage(HttpMethod.Get, ConfigurationsEndpoint);
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
var configurations = await response.Content.ReadFromJsonAsync<IEnumerable<Info.ConfigurationInfo>>();
#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one
configurations = configurations?.OrderBy(x => x.DisplayOrder)
?? throw new ArgumentNullException(nameof(configurations));
#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one
return configurations;
}
#endregion
}
public class VaultUserInfo
{
public string vaultTitle { get; set; }
public string vaultUserName { get; set; }
public string userName { get; set; }
public string fullName { get; set; }
}
}
@@ -0,0 +1,30 @@
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
public static class HttpMessageResponseExtensions
{
public static void AssertIsValid(this HttpResponseMessage response, bool exportable = false, string version = "1.0")
{
Assert.Multiple(() =>
{
Assert.That(response.IsSuccessStatusCode, Is.True);
var headers = response.Headers.Where(x => x.Value != null)
.ToDictionary(k => k.Key, v => v.Value);
Assert.That(headers, Is.Not.Null.And.Not.Empty);
Assert.That(headers.ContainsKey("Api-Supported-Versions"), Is.True);
var apiVersion = headers["Api-Supported-Versions"].ToList();
Assert.That(apiVersion.Contains(version), Is.True);
if (exportable)
{
var content = response.Content;
Assert.That(content, Is.Not.Null);
headers = response.Content.Headers.Where(x => x.Value != null)
.ToDictionary(k => k.Key, v => v.Value);
Assert.That(headers.ContainsKey("Content-Type"), Is.True);
var contentType = headers["Content-Type"].ToList();
Assert.That(contentType.Contains("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), Is.True);
}
});
}
}
}
@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
public class StrataTokenInfo
{
[JsonPropertyName("access_token")]
public required string AccessToken { get; set; }
[JsonPropertyName("token_type")]
public required string TokenType { get; set; }
[JsonPropertyName("expires_in")]
public required long ExpiresIn { get; set; }
[JsonPropertyName("refresh_token")]
public required string RefreshToken { get; set; }
}
}
@@ -0,0 +1,69 @@
using Strata.ApiCommunication.Http;
using Strata.ApiLib.Standard.Models;
using Strata.ContinuousImprovement.Biz.Generics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Json;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[ExcludeFromCodeCoverage]
[TestFixture(Category = "Integration")]
public class TestAllOpportunitiesController : ApiTestBase
{
protected const string AllOpportunitiesEndpoint = "api/v1/all-opportunities";
[Test]
public async Task TestGetAllOportunitiesFromApi()
{
// arrange
var configurations = await GetConfigurations();
var configuration = configurations.First();
var loadOptions = new Controllers.AllOpportunityLoadOptions
{
Filters = new Biz.All.Opportunities.Filters.AllFilters
{
ConfigurationGuid = configuration.ConfigurationGuid,
OpportunityTypes =
[
$"{(int)OpportunityTypes.Encounter}",
$"{(int) OpportunityTypes.QualityVariation}",
$"{(int) OpportunityTypes.Variation}",
],
ViewVisible = true
},
PagingOptions = new Biz.Pagination.PagingOptions
{
First = 0,
Rows = 100,
SortOrder = Biz.Pagination.SortOrder.Desc,
SortField = "IdentifiedSavings"
}
};
var request = new HttpRequestMessage(HttpMethod.Post, AllOpportunitiesEndpoint);
// act
await request.SetContentAsJsonAsync(loadOptions);
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
// assert
Assert.Multiple(() =>
{
var opportunitiesPage = new PagedApiResponse<Info.AllOpportunityInfo>();
Assert.DoesNotThrowAsync(async () =>
opportunitiesPage = await response.Content.ReadFromJsonAsync<PagedApiResponse<Info.AllOpportunityInfo>>());
response.AssertIsValid();
Assert.That(opportunitiesPage.Data, Is.Not.Empty);
Assert.That(opportunitiesPage.Data.Count, Is.LessThanOrEqualTo(loadOptions.PagingOptions.Rows));
Assert.That(opportunitiesPage.Data.Any(opp =>
opp.OpportunityType == (int)OpportunityTypes.Encounter
|| opp.OpportunityType == (int)OpportunityTypes.QualityVariation
|| opp.OpportunityType == (int)OpportunityTypes.Variation), Is.True);
Assert.That(opportunitiesPage.PagingInfo.ResultsPerPage, Is.LessThanOrEqualTo(loadOptions.PagingOptions.Rows));
});
}
}
}
@@ -0,0 +1,34 @@
using Strata.DecisionSupport.Models.Jazz;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Json;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[ExcludeFromCodeCoverage]
[TestFixture(Category = "Integration")]
public class TestClientOpportunityWorkbooksController : ApiTestBase
{
protected const string ClientOpportunityWorkbooksEndpoint = "api/v1/client-opportunity-workbooks";
[Test]
public async Task TestGetAllWorkbooksFromApi()
{
// arrange
var request = new HttpRequestMessage(HttpMethod.Get, $"{ClientOpportunityWorkbooksEndpoint}/workbooks");
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
var workbooks = await response.Content.ReadFromJsonAsync<IEnumerable<WorkbookClientInfo>>(CancellationToken.None);
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(workbooks, Is.AssignableTo<IEnumerable<WorkbookClientInfo>>());
Assert.That(workbooks, Is.Not.Null.And.Not.Empty);
});
}
}
}
@@ -0,0 +1,170 @@
using Microsoft.AspNetCore.Http;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Json;
using System.Text.RegularExpressions;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[ExcludeFromCodeCoverage]
[TestFixture, Category("Integration"), Category("API")]
public partial class TestConfigurationsController : ApiTestBase
{
protected const string ConfigurationsEndpoint = "api/v1/configurations";
[Test]
public async Task TestConfigurations()
{
//arrange
var request = new HttpRequestMessage(HttpMethod.Get, ConfigurationsEndpoint);
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
// assert
response.EnsureSuccessStatusCode();
response.AssertIsValid();
}
[Test]
[Ignore("Test database needs to be rolled over to the new year")]
public async Task TestDfltConfiguration()
{
//arrange
var request = new HttpRequestMessage(HttpMethod.Get, ConfigurationsEndpoint);
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
var configurations = await response.Content.ReadFromJsonAsync<IEnumerable<Info.ConfigurationInfo>>();
configurations = configurations?.OrderBy(x => x.DisplayOrder);
var configuration = configurations?.FirstOrDefault();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(configurations, Is.Not.Null.And.Not.Empty);
Assert.That(configuration, Is.Not.Null);
var expectedFiscalYearId = DateTime.Now.Year;
Assert.That(configuration.FiscalYearId, Is.InRange(expectedFiscalYearId - 1, expectedFiscalYearId), "Test database fiscal year configuration must be updated for the current year.");
});
}
[Test]
public async Task TestEntityConfiguration()
{
//arrange
var request = new HttpRequestMessage(HttpMethod.Get, ConfigurationsEndpoint);
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
var configurations = await response.Content.ReadFromJsonAsync<IEnumerable<Info.ConfigurationInfo>>();
configurations = configurations?.OrderBy(x => x.DisplayOrder);
var configuration = configurations?.FirstOrDefault(c => c.EntityIdsCsv != "" && !c.IsUsingCustomEntityDimension);
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(configurations, Is.Not.Null.And.Not.Empty);
Assert.That(configuration, Is.Not.Null);
Assert.That(configuration.IsUsingCustomEntityDimension, Is.False);
Assert.That(configuration.EntityIdsCsv, Is.Not.Empty);
});
}
[Test]
public async Task TestConfigurationEntities()
{
//arrange
var configurations = await GetConfigurations();
var configurationGuid = configurations?.FirstOrDefault(c => c.EntityIdsCsv != "" && !c.IsUsingCustomEntityDimension)?.ConfigurationGuid ?? Guid.Empty;
var configurationsEndpoint = ConfigurationsEndpoint + $"/{configurationGuid}/configuration-entities";
var request = new HttpRequestMessage(HttpMethod.Get, configurationsEndpoint);
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
var configurationEntities = await response.Content.ReadFromJsonAsync<IEnumerable<Info.EntityIdInfo>>();
configurationEntities = configurationEntities?.OrderBy(x => x.EntityName);
var configurationEntity = configurationEntities?.FirstOrDefault();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(configurationEntities, Is.Not.Null.And.Not.Empty);
Assert.That(configurationEntity, Is.Not.Null);
});
}
[Test]
public async Task TestConfigurationServiceLineInfo()
{
//arrange
var configurations = await GetConfigurations();
var configurationGuid = configurations?.FirstOrDefault()?.ConfigurationGuid ?? Guid.Empty;
var configurationsEndpoint = ConfigurationsEndpoint + $"/{configurationGuid}/configuration-service-line-info";
var request = new HttpRequestMessage(HttpMethod.Get, configurationsEndpoint);
var guidRegex = GuidRegex();
var serviceLineRegex = new Regex("w*Service Lines?");
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
var configurationServiceLine = await response.Content.ReadFromJsonAsync<Info.ServiceLineInfo>();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(configurationServiceLine, Is.Not.Null);
Assert.That(configurationServiceLine.ServiceLineGlobalId, Is.Not.Null);
Assert.That(configurationServiceLine.ServiceLineTitle, Is.Not.Null.And.Not.Empty);
Assert.That(configurationServiceLine.ServiceLineName, Is.Not.Null.And.Not.Empty);
Assert.That(guidRegex.IsMatch(configurationServiceLine.ServiceLineDimensionGuid), Is.True);
Assert.That(guidRegex.IsMatch(configurationServiceLine.ServiceLineHierarchicalGuid), Is.True);
Assert.That(serviceLineRegex.IsMatch(configurationServiceLine.ServiceLineTitle), Is.True);
Assert.That(serviceLineRegex.IsMatch(configurationServiceLine.ServiceLineName), Is.True);
});
}
[Test]
public async Task TestConfigurationEntityInfo()
{
//arrange
var configurations = await GetConfigurations();
var configurationGuid = configurations?.FirstOrDefault()?.ConfigurationGuid ?? Guid.Empty;
var configurationsEndpoint = ConfigurationsEndpoint + $"/{configurationGuid}/configuration-entity-info";
var request = new HttpRequestMessage(HttpMethod.Get, configurationsEndpoint);
var guidRegex = GuidRegex();
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
var configurationServiceLine = await response.Content.ReadFromJsonAsync<Info.EntityInfo>();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(configurationServiceLine, Is.Not.Null);
Assert.That(configurationServiceLine.DepartmentRollupGlobalId, Is.Not.Null.And.Not.Empty);
Assert.That(configurationServiceLine.EntityTitle, Is.Not.Null.And.Not.Empty);
Assert.That(configurationServiceLine.EntityLabel, Is.Not.Null.And.Not.Empty);
Assert.That(configurationServiceLine.EntityText, Is.Not.Null.And.Not.Empty);
Assert.That(guidRegex.IsMatch(configurationServiceLine.HierarchicalGuid), Is.True);
});
}
[GeneratedRegex("[({]?[a-fA-F0-9]{8}[\\-]?([a-fA-F0-9]{4}[\\-]?){3}[a-fA-F0-9]{12}[})]?")]
private static partial Regex GuidRegex();
}
}
@@ -0,0 +1,147 @@
using Strata.ApiCommunication.Http;
using Strata.ApiLib.Standard.Models;
using Strata.ContinuousImprovement.Api.Test.Integration.Mapperly;
using Strata.ContinuousImprovement.Biz.Dtos;
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities;
using Strata.ContinuousImprovement.Biz.OrgDefined.Queries;
using Strata.ContinuousImprovement.Biz.Shared;
using System.Net.Http.Json;
using System.Text.Json;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[TestFixture, Category("Integration"), Category("API")]
public class TestEncounterOpportunitiesController : ApiTestBase
{
private const string EncounterOpportunitiesEndpoint = "api/v1/encounter-opportunities";
[Test]
public async Task OpportunityMetricCohortDataTest()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, "api/v1/configurations");
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var configurations = await response.Content
.ReadFromJsonAsync<List<ConfigurationInfo>>(options, CancellationToken.None);
var configuration = configurations.SingleOrDefault(c => c.DisplayOrder == 0);
if (configuration == null) Assert.Inconclusive("Unable to obtain configuration");
request = new HttpRequestMessage(HttpMethod.Post, EncounterOpportunitiesEndpoint);
await request.SetContentAsJsonAsync(
new
{
pagingOptions = new
{
sortField = "IdentifiedSavings",
sortOrder = "Asc",
first = 0,
rows = 1
},
filters = new
{
configurationGuid = configuration.ConfigurationGuid,
identifiedSavings = 0,
viewVisible = true,
excludeInitiaves = false,
opportunitySearch = "",
cases = 0,
opportunityLevels = Array.Empty<string>(),
encounterGroups = "CaseType",
seasonality = "Without"
}
});
response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var opportunityPage =
await response.Content.ReadFromJsonAsync<PagedApiResponse<ENOpportunity>>(options, CancellationToken.None);
var opportunity = opportunityPage?.Data.FirstOrDefault(opp => opp.OpportunityLevel == nameof(OpportunityLevel.Entity));
if (opportunity == null) Assert.Ignore("No Encounter Opportunity found to work with");
request = new HttpRequestMessage(HttpMethod.Get, EncounterOpportunitiesEndpoint + $"/{opportunity.OpportunityGuid}");
response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var opportunityDto = await response.Content.ReadFromJsonAsync<ENOpportunityDto>(options, CancellationToken.None);
if (opportunityDto == null) Assert.Ignore("Unable to retrieve opportunity data");
var data = opportunityDto.Map();
var caseTypeCategorySql = string.Empty;
/*
var CaseTypeCategories = await jazzDbContext.DimCaseTypes
.GroupBy(ct => ct.CaseTypeCategory, (g, d) => new
{
CaseTypeCategory = g,
CaseTypeIds = d.Select(x => x.CaseTypeId).ToList()
}).ToListAsync(CancellationToken.None);
var CaseTypeCategorySql = CaseTypeCategories
.Select(ct => string.Format(CaseTypeFamilyIdExists.Exists[ct.CaseTypeCategory], string.Join(",", ct.CaseTypeIds)))
.Aggregate("", (x, y) => $"{x}\r\nOR\r\n{y}");
*/
var requestInfo = new OpportunityMetricCohortOptions
{
ConfigurationGuid = configuration.ConfigurationGuid,
Data = data,
InnerJoin = caseTypeCategorySql
};
request = new HttpRequestMessage(HttpMethod.Post, $"{EncounterOpportunitiesEndpoint}/opportunity-metric-cohort-data");
await request.SetContentAsJsonAsync(requestInfo, CancellationToken.None);
// Act
response = await HttpClient.SendAsync(request);
// Assert
response.EnsureSuccessStatusCode();
}
}
public class ConfigurationInfo
{
public string Name { get; set; }
public Guid ConfigurationGuid { get; set; }
public int DisplayOrder { get; set; }
public int FiscalYearId { get; set; }
}
public class ENOpportunityInfo
{
public Guid OpportunityGuid { get; set; }
public Guid ConfigurationGuid { get; set; }
public string OpportunityKey { get; set; }
public double IdentifiedSavings { get; set; }
public Guid InitiativeGuid { get; set; }
public bool HasInitiative => InitiativeGuid != Guid.Empty;
public string Initiative { get; set; }
public bool IsHidden { get; set; }
public string Note { get; set; } = String.Empty;
public string Name { get; set; }
public double Cases { get; set; }
public string EncounterGroup { get; set; }
public string EncounterGroupValue { get; set; }
public string OpportunityLevel { get; set; }
public bool IsSeasonal { get; set; }
public long OpportunityId { get; set; }
}
public static class CaseTypeFamilyIdExists
{
public static string[] Exists => new[]
{
@" EXISTS(SELECT 1 FROM dss.CaseTypeFamilyMappingICD10 msdrg
INNER JOIN dss.DimCaseType dct ON dct.CaseTypeFamilyID = msdrg.CaseTypeFamilyID AND dct.CaseTypeId in ({0}) AND dct.CaseTypeCategory = 0
WHERE msdrg.CaseTypeFamilyID = fpes.MSDRGCaseTypeFamilyID)",
@" EXISTS(SELECT 1 FROM dss.CaseTypeFamilyMappingAPRDRG aprdrg
INNER JOIN dss.DimCaseType dct ON dct.CaseTypeFamilyID = aprdrg.CaseTypeFamilyID AND dct.CaseTypeId in ({0}) AND dct.CaseTypeCategory = 1
WHERE aprdrg.CaseTypeFamilyID = fpes.APRDRGCaseTypeFamilyID)",
@"AND EXISTS(SELECT 1 FROM dss.FactPatientEncounterClinicalIndicator ci
WHERE ci.EncounterID = fpes.EncounterID AND ci.ClinicalIndicatorID in ({0}) and ci.IsDeleted = 0)",
@" EXISTS(SELECT 1 FROM dss.CaseTypeFamilyMappingCPT cpt
INNER JOIN dss.DimCaseType dct ON dct.CaseTypeFamilyID = cpt.CaseTypeFamilyID AND dct.CaseTypeId in ({0}) AND dct.CaseTypeCategory = 3
WHERE cpt.CaseTypeFamilyID = fpes.CPTCodeCaseTypeFamilyID)"
};
}
}
@@ -0,0 +1,26 @@
using Strata.ContinuousImprovement.Api.Test.Integration.Setup;
using Strata.ContinuousImprovement.Biz.DbContexts;
using System.Diagnostics.CodeAnalysis;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[ExcludeFromCodeCoverage]
[TestFixture(Category = "Integration")]
public class TestFixtureSetup : ApiTestBase
{
[Test]
public async Task TestBearerToken()
{
var bearerToken = AccessToken;
Assert.That(bearerToken, Is.Not.Null.And.Not.Empty);
}
[Test]
public async Task TestCentralDbContext()
{
var centralDbContext = ServiceProperties.CentralDbContext as CentralDbContext;
Assert.That(centralDbContext, Is.Not.Null);
}
}
}
@@ -0,0 +1,188 @@
using Strata.ApiCommunication.Http;
using System.Net.Http.Json;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[TestFixture(Category = "Integration")]
public class TestInitiativesController : ApiTestBase
{
protected const string InitiativesEndpoint = "api/v1/initiatives";
protected const string InitiativesDetailColumnsEndpoint = "api/v1/initiative-detail-columns";
[Test]
public async Task TestInitiatives()
{
// arrange
var configurations = await GetConfigurations();
var configuration = configurations.OrderBy(c => c.DisplayOrder).First();
var configurationGuid = configuration.ConfigurationGuid;
var data = new
{
configurationGuid,
name = "",
status = 0,
costDrivers = Array.Empty<string>(),
costLeaders = Array.Empty<string>(),
owners = Array.Empty<string>(),
executiveSponsors = Array.Empty<string>(),
workstreams = Array.Empty<string>(),
rollupColumn1Values = Array.Empty<string>(),
rollupColumn2Values = Array.Empty<string>(),
rollupColumn3Values = Array.Empty<string>(),
groupByCategory = "CostDriver"
};
var request = new HttpRequestMessage(HttpMethod.Post, InitiativesEndpoint);
await request.SetContentAsJsonAsync(data);
// act
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var initiatives = await response.Content.ReadFromJsonAsync<IEnumerable<dynamic>>();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(initiatives, Is.Not.Null.And.Not.Empty);
});
}
[Test]
public async Task TestInitiativesCriteria()
{
// arrange
var request = new HttpRequestMessage(HttpMethod.Get, InitiativesEndpoint + "/criteria");
// act
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var initiativesCriteria = await response.Content.ReadFromJsonAsync<IEnumerable<dynamic>>();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(initiativesCriteria, Is.Not.Null.And.Not.Empty);
});
}
[Test]
public async Task TestInitiativesDetailColumnList()
{
// arrange
var request = new HttpRequestMessage(HttpMethod.Get, "api/v1/initiative-detail-columns/column-list");
// act
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var detailColumns = await response.Content.ReadFromJsonAsync<IEnumerable<dynamic>>();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(detailColumns, Is.Not.Null.And.Not.Empty);
});
}
[Test]
public async Task TestInitiativesDefaultColumnList()
{
// arrange
var request = new HttpRequestMessage(HttpMethod.Get, "api/v1/initiative-detail-columns/default-column-list");
// act
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var detailColumns = await response.Content.ReadFromJsonAsync<IEnumerable<dynamic>>();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(detailColumns, Is.Not.Null.And.Not.Empty);
});
}
[Test]
public async Task TestInitiativesFilterOptions()
{
// arrange
var configurations = await GetConfigurations();
var configuration = configurations.OrderBy(c => c.DisplayOrder).First();
var configurationGuid = configuration.ConfigurationGuid;
var request = new HttpRequestMessage(HttpMethod.Get, InitiativesEndpoint + $"/{configurationGuid}/filter-options");
// act
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var filterOptions = await response.Content.ReadAsStringAsync();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(filterOptions, Is.Not.Null.And.Not.Empty);
});
}
[Test]
public async Task TestInitiativesFilterSettings()
{
// arrange
var configurations = await GetConfigurations();
var configuration = configurations.OrderBy(c => c.DisplayOrder).First();
var configurationGuid = configuration.ConfigurationGuid;
var request = new HttpRequestMessage(HttpMethod.Get, InitiativesEndpoint + $"/filter-settings");
// act
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var filterSettings = await response.Content.ReadAsStringAsync();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(filterSettings, Is.Not.Null.And.Not.Empty);
});
}
[Test]
public async Task TestInitiativesExport()
{
// arrange
var configurations = await GetConfigurations();
var configuration = configurations.OrderBy(c => c.DisplayOrder).First();
var configurationGuid = configuration.ConfigurationGuid;
var data = new
{
sortOptions = new
{
sortField = "name",
sortOrder = "Asc"
},
filters = new
{
configurationGuid,
groupByCategory = "CostDriver"
}
};
var request = new HttpRequestMessage(HttpMethod.Post, InitiativesEndpoint + "/export?exportAll=true");
await request.SetContentAsJsonAsync(data);
// act
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
// assert
response.AssertIsValid(true);
}
}
}
@@ -0,0 +1,135 @@
using Strata.ApiCommunication.Http;
using Strata.ContinuousImprovement.Api.Test.Integration.Info;
using System.Net.Http.Json;
using System.Text.RegularExpressions;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[TestFixture(), Category("Integration"), Category("API")]
public class TestInitiativesController_Export : ApiTestBase
{
protected const string InitiativesEndpoint = "api/v1/initiatives";
private Guid ConfigurationGuid { get; set; } = Guid.Empty;
private string Workstream { get; set; } = string.Empty;
private object FilterSettings { get; set; }
[Test, Order(1)]
public async Task TestInitiativesExport_Step1()
{
// arrange
// act
var configurations = await GetConfigurations();
var configuration = configurations.OrderBy(c => c.DisplayOrder).First();
var configurationGuid = configuration.ConfigurationGuid;
ConfigurationGuid = configurationGuid;
// assert
Assert.That(ConfigurationGuid, Is.Not.EqualTo(Guid.Empty));
}
[Test, Order(2)]
public async Task TestInitiativesExport_Step2()
{
// arrange
var request = new HttpRequestMessage(HttpMethod.Get, InitiativesEndpoint + $"/{ConfigurationGuid}/filter-options");
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var filterOptions = await response.Content.ReadFromJsonAsync<InitiativeFilterOptionsInfo>();
// assert
Assert.Multiple(() =>
{
Assert.That(filterOptions, Is.Not.Null);
Assert.That(filterOptions.Workstreams, Is.Not.Null.And.Not.Empty);
});
var workstream = filterOptions.Workstreams.First().Name;
Workstream = workstream;
}
[Test, Order(3)]
public async Task TestInitiativesExport_Step3()
{
// arrange
FilterSettings = new
{
ConfigurationGuid,
name = "",
status = 0,
costDrivers = Array.Empty<string>(),
costLeaders = Array.Empty<string>(),
owners = Array.Empty<string>(),
executiveSponsors = Array.Empty<string>(),
workstreams = new string[] { Workstream },
rollupColumn1Values = Array.Empty<string>(),
rollupColumn2Values = Array.Empty<string>(),
rollupColumn3Values = Array.Empty<string>(),
groupByCategory = "CostDriver"
};
// act
var request = new HttpRequestMessage(HttpMethod.Post, InitiativesEndpoint + "/set-filter-settings");
await request.SetContentAsJsonAsync(FilterSettings);
var response = await HttpClient.SendAsync(request);
// assert
response.EnsureSuccessStatusCode();
}
[Test, Order(4)]
public async Task TestInitiativesExport_Step4()
{
// arrange
var request = new HttpRequestMessage(HttpMethod.Post, InitiativesEndpoint);
await request.SetContentAsJsonAsync(FilterSettings);
var response = await HttpClient.SendAsync(request);
// act
response.EnsureSuccessStatusCode();
}
[Test, Order(5)]
public async Task TestInitiativesExport_Step5()
{
// arrange
var data = new
{
sortOptions = new
{
sortField = "name",
sortOrder = "Asc"
},
filters = new
{
ConfigurationGuid,
name = "",
status = 0,
costDrivers = Array.Empty<string>(),
costLeaders = Array.Empty<string>(),
owners = Array.Empty<string>(),
executiveSponsors = Array.Empty<string>(),
workstreams = new string[] { Workstream },
rollupColumn1Values = Array.Empty<string>(),
rollupColumn2Values = Array.Empty<string>(),
rollupColumn3Values = Array.Empty<string>(),
groupByCategory = "CostDriver"
}
};
var request = new HttpRequestMessage(HttpMethod.Post, InitiativesEndpoint + "/export");
await request.SetContentAsJsonAsync(data);
// act
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
// assert
var headers = response.Content.Headers.Where(x => x.Value != null)
.ToDictionary(k => k.Key, v => v.Value);
var disposition = headers["Content-Disposition"].First();
var regex = new Regex("attachment; filename=Initiatives_[\\d]{2}-[\\d]{2}-[\\d]{4}\\.xlsx; filename\\*=UTF-8''Initiatives_[\\d]{2}-[\\d]{2}-[\\d]{4}\\.xlsx",
RegexOptions.IgnoreCase);
Assert.That(regex.IsMatch(disposition));
}
}
}
@@ -0,0 +1,167 @@
using Strata.ApiCommunication.Http;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[TestFixture(Category = "Integration")]
public class TestOpportunityController : ApiTestBase
{
[TestCaseSource(nameof(OpportunityControllerTestCases))]
public async Task OpportunityControllerTest(string testName, string url, string sortField, object? filters, bool exportable)
{
// arrange
var configurations = await GetConfigurations();
var configuration = configurations.OrderBy(c => c.DisplayOrder).First();
var configurationGuid = configuration.ConfigurationGuid;
sortField = sortField ?? "identifiedSavings";
filters = filters ?? new
{
configurationGuid,
opportunityTypes = Array.Empty<string>(),
identifiedSavings = null as object,
viewVisible = true,
excludeInitiatives = false,
opportunitySearch = ""
};
var data = new
{
pagingOptions = new
{
sortField,
sortOrder = "Desc",
first = 0,
rows = 25
},
filters,
};
var request = new HttpRequestMessage(HttpMethod.Post, url);
await request.SetContentAsJsonAsync(data);
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
// asert
Assert.Multiple(() =>
{
response.AssertIsValid();
});
}
[TestCaseSource(nameof(OpportunityControllerTestCases))]
public async Task OpportunityControllerFilterTest(string testName, string url, string sortField, object? filters, bool exportable)
{
// arrange
var configurations = await GetConfigurations();
var configuration = configurations.OrderBy(c => c.DisplayOrder).First();
var configurationGuid = configuration.ConfigurationGuid;
var request = new HttpRequestMessage(HttpMethod.Get, url + $"/{configurationGuid}/filter-options");
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
// asert
Assert.Multiple(() =>
{
response.AssertIsValid();
});
}
[TestCaseSource(nameof(OpportunityControllerTestCases))]
public async Task OpportunityControllerFilteExportTest(string testName, string url, string sortField, object? filters, bool exportable)
{
if (!exportable) { Assert.Ignore(); return; }
// arrange
var configurations = await GetConfigurations();
var configuration = configurations.OrderBy(c => c.DisplayOrder).First();
var configurationGuid = configuration.ConfigurationGuid;
sortField = sortField ?? "identifiedSavings";
filters = filters ?? new
{
configurationGuid,
opportunityTypes = Array.Empty<string>(),
identifiedSavings = null as object,
viewVisible = true,
excludeInitiatives = false,
opportunitySearch = ""
};
var data = new
{
pagingOptions = new
{
sortField,
sortOrder = "Desc",
first = 0,
rows = 25
},
filters,
};
var request = new HttpRequestMessage(HttpMethod.Post, url + $"/export");
await request.SetContentAsJsonAsync(data);
// act
var response = await HttpClient.SendAsync(request, CancellationToken.None);
response.EnsureSuccessStatusCode();
// asert
Assert.Multiple(() =>
{
response.AssertIsValid(exportable);
});
}
public static IEnumerable<TestCaseData> OpportunityControllerTestCases()
{
yield return new TestCaseData("all opportunities", "/api/v1/all-opportunities", null, new
{
configurationGuid = "aa4cba4f-2506-462c-8e34-c45b21653988"
}, true).SetName("{m} {0}");
yield return new TestCaseData("charge code opportunities", "/api/v1/charge-code-opps", null, new
{
configurationGuid = "aa4cba4f-2506-462c-8e34-c45b21653988",
opportunityTypes = Array.Empty<string>(),
identifiedSavings = null as object,
viewVisible = true,
excludeInitiatives = false,
opportunitySearch = ""
}, true).SetName("{m} {0}");
yield return new TestCaseData("encounter opportunities", "/api/v1/encounter-opportunities", null, null, true)
.SetName("{m} {0}");
yield return new TestCaseData("free form opportunities", "/api/v1/free-form-opportunities", null, null, true)
.SetName("{m} {0}");
yield return new TestCaseData("general ledger opportunities", "/api/v1/general-ledger-opportunities", null, null, true)
.SetName("{m} {0}");
yield return new TestCaseData("payroll opportunities", "/api/v1/payroll-opportunities", null, null, true)
.SetName("{m} {0}");
yield return new TestCaseData("qvi opportunities", "/api/v1/quality-variation-item-opportunities", null, null, true)
.SetName("{m} {0}");
yield return new TestCaseData("qvi breakdowns", "/api/v1/quality-variation-item-breakdowns", "identifiedSavingsTotal", new
{
exportFiltered = false,
configurationGuid = "aa4cba4f-2506-462c-8e34-c45b21653988",
identifiedSavings = 0,
breakdownIds = Array.Empty<string>(),
qualityVariationCases = 0
}, false).SetName("{m} {0}");
yield return new TestCaseData("qvi events", "/api/v1/quality-variation-item-events", "identifiedSavingsTotal", new
{
configurationGuid = "aa4cba4f-2506-462c-8e34-c45b21653988"
}, false).SetName("{m} {0}");
yield return new TestCaseData("variation opportunities", "/api/v1/variation-opportunities", null, new
{
configurationGuid = "aa4cba4f-2506-462c-8e34-c45b21653988"
}, true).SetName("{m} {0}");
}
}
}
@@ -0,0 +1,30 @@
using Strata.ContinuousImprovement.Api.Test.Integration.Info;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Json;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[ExcludeFromCodeCoverage]
[TestFixture(Category = "Integration")]
public class TestSecuirtyController : ApiTestBase
{
[Test]
public async Task TestSecurityIdenties()
{
// arrange
var request = new HttpRequestMessage(HttpMethod.Get, "/api/v1/security/identities");
// act
var response = await HttpClient.SendAsync(request);
var securityInfos = await response.Content.ReadFromJsonAsync<IEnumerable<SecurityInfo>>();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
Assert.That(securityInfos, Is.Not.Null.And.Not.Empty);
Assert.That(securityInfos.Any(s => s.Title == "Administrator, CCI"), Is.True);
});
}
}
}
@@ -0,0 +1,42 @@
using System.Diagnostics.CodeAnalysis;
namespace Strata.ContinuousImprovement.Api.Test.Integration.Tests
{
[ExcludeFromCodeCoverage]
[TestFixture(Category = "Integration")]
public class TestUsersController : ApiTestBase
{
protected const string UsersEndpoint = "api/v1/users";
[TestCaseSource(nameof(TestUserCases))]
public async Task TestUsers(string testName, string url, bool hasCostLeader, bool hasAaronHooper)
{
// arrange
var request = new HttpRequestMessage(HttpMethod.Get, UsersEndpoint + $"/{url}");
// act
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
// assert
Assert.Multiple(() =>
{
response.AssertIsValid();
});
}
public static IEnumerable<TestCaseData> TestUserCases()
{
yield return new TestCaseData("administrators", "administrators", true, false)
.SetName("{m} - {0}");
yield return new TestCaseData("cost leaders", "cost-leaders", true, false)
.SetName("{m} - {0}");
yield return new TestCaseData("executive sponsors", "executive-sponsors", true, false)
.SetName("{m} - {0}");
yield return new TestCaseData("owners", "owners", true, true)
.SetName("{m} - {0}");
yield return new TestCaseData("users", "users", true, true)
.SetName("{m} - {0}");
}
}
}
@@ -0,0 +1,52 @@
using ClosedXML.Excel;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using NUnit.Framework;
using System;
using System.Threading.Tasks;
using NUnitSetup = NUnit.Framework.SetUpAttribute;
namespace Strata.ContinuousImprovement.Api.Test.Unit.ActionResults
{
[TestFixture, Category("Unit"), Category("ContentResult")]
public class ExcelContentResultTests
{
public ExcelContentResult ExcelContentResult { get; set; }
public IXLWorkbook Workbook { get; set; }
[NUnitSetup]
public void Setup()
{
Workbook = new XLWorkbook();
Workbook.AddWorksheet();
ExcelContentResult = new ExcelContentResult(Workbook, "Export");
}
[Test()]
public void ExcelContentResultTest()
{
// Assert
ExcelContentResult.Should().NotBeNull();
ExcelContentResult.Workbook.Should().NotBeNull();
ExcelContentResult.FileName.Should().NotBeNullOrEmpty();
}
[Test()]
public void ExecuteResultTest()
{
// Arange
var actionConext = new ActionContext();
Action executeResult = () => ExcelContentResult.ExecuteResult(actionConext);
executeResult.Should().NotThrow();
}
[Test(), Ignore("We don't currently use this implementation method")]
public async Task ExecuteResultAsyncTest()
{
// Arange
var actionConext = new ActionContext();
Assert.DoesNotThrowAsync(async () => await ExcelContentResult.ExecuteResultAsync(actionConext));
}
}
}
@@ -0,0 +1,221 @@
using ClosedXML.Excel;
using Microsoft.Extensions.Logging;
using Moq;
using MoreLinq;
using NUnit.Framework;
using Strata.ApiLib.Standard.Models;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.All;
using Strata.ContinuousImprovement.Biz.All.Opportunities;
using Strata.ContinuousImprovement.Biz.All.Opportunities.Filters;
using Strata.ContinuousImprovement.Biz.Export;
using Strata.ContinuousImprovement.Biz.Filter;
using Strata.ContinuousImprovement.Biz.Generics;
using Strata.ContinuousImprovement.Biz.Pagination;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.FeatureFlags.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class AllOpportunityControllerTests
{
public const string ConfigurationGuid = "aa4cba4f-2506-462c-8e34-c45b21653988";
[TestCaseSource(nameof(GetOpportunityTestCases))]
public async Task GetOpportunityTest(bool featureFlagSet, bool featureFlagIsOn, bool isSdtEmployee = true)
{
var result = await GetPagedApiResponse(featureFlagSet);
var service = await GetAllOpportunityService(result);
var claimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor(isSdtEmployee: isSdtEmployee);
var featureFlagServiceClient = featureFlagSet
? TestUtilities.GetFeatureFlagServiceClient("ciflexibleexplorationenabled", featureFlagIsOn)
: Mock.Of<IFeatureFlagServiceClient>();
var controller = new AllOpportunitiesController(service, claimsPrincipalAccessor,
featureFlagServiceClient,
Mock.Of<ILogger<AllOpportunitiesController>>());
var pagedResponse = await controller.GetOpportunities(new AllOpportunityLoadOptions
{
PagingOptions = new PagingOptions { First = 0, Rows = 20, SortField = "IdentifiedSavings", SortOrder = SortOrder.Desc },
Filters = new AllFilters
{
ConfigurationGuid = new Guid(ConfigurationGuid)
}
}, CancellationToken.None);
Assert.That(result, Is.SameAs(pagedResponse));
}
public static IEnumerable<TestCaseData> GetOpportunityTestCases()
{
yield return new TestCaseData(true, false, true)
.SetName(nameof(GetOpportunityTest) + " FeatureFlag Off SdtEmployee");
yield return new TestCaseData(true, true, true)
.SetName(nameof(GetOpportunityTest) + " FeatureFlag On SdtEmployee");
yield return new TestCaseData(true, false, false)
.SetName(nameof(GetOpportunityTest) + " FeatureFlag Off Not SdtEmployee");
yield return new TestCaseData(true, true, false)
.SetName(nameof(GetOpportunityTest) + " FeatureFlag On Not SdtEmployee");
yield return new TestCaseData(false, false, true)
.SetName(nameof(GetOpportunityTest) + " No FeatureFlag Off SdtEmployee");
yield return new TestCaseData(false, true, true)
.SetName(nameof(GetOpportunityTest) + " No FeatureFlag On SdtEmployee");
yield return new TestCaseData(false, false, false)
.SetName(nameof(GetOpportunityTest) + " No FeatureFlag Off Not SdtEmployee");
yield return new TestCaseData(false, true, false)
.SetName(nameof(GetOpportunityTest) + " No FeatureFlag On Not SdtEmployee");
}
[TestCase(false)]
[TestCase(true)]
public async Task GetNameIsValidTest(bool isValid)
{
var result = await GetPagedApiResponse();
var service = await GetAllOpportunityService(result);
var claimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
var firstRow = result.Data.First();
var validateName = new ValidateName
{
ConfigurationGuid = firstRow.ConfigurationGuid,
OpportunityGuid = isValid ? Guid.NewGuid() : firstRow.OpportunityGuid,
Name = firstRow.Name,
OpportunityType = firstRow.OpportunityType
};
var controller = new AllOpportunitiesController(service, claimsPrincipalAccessor,
Mock.Of<IFeatureFlagServiceClient>(),
Mock.Of<ILogger<AllOpportunitiesController>>());
var isNameValid = await controller.IsNameValid(validateName, CancellationToken.None);
Assert.That(isValid, Is.EqualTo(isNameValid));
}
[TestCase(true)]
[TestCase(false)]
public async Task GetFilterOptionsTest(bool isSdtEmployee)
{
var result = await GetPagedApiResponse();
var service = await GetAllOpportunityService(result);
var claimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor(isSdtEmployee: isSdtEmployee);
var controller = new AllOpportunitiesController(service, claimsPrincipalAccessor,
Mock.Of<IFeatureFlagServiceClient>(),
Mock.Of<ILogger<AllOpportunitiesController>>());
var filterOptions = new AllFilterOptions
{
OpportunityTypes = Enum.GetValues<OpportunityTypes>()
.Where(ot => isSdtEmployee || ot != OpportunityTypes.Blank)
.Select(ot => new FilterMember { Id = $"{(int)ot}", Text = ot.OpportunityTypeName() })
};
var options = await controller.GetFilterOptionsAsync(result.Data.First().ConfigurationGuid, CancellationToken.None);
filterOptions.OpportunityTypes
.ForEach(ot => Assert.That(options.OpportunityTypes.Any(x => x.Id == ot.Id && x.Text == ot.Text), Is.True));
}
[Test, Ignore("Ignoring because we don't have generic GetById endpoint to use here.")]
public async Task SetHiddenAsyncTest()
{
var result = await GetPagedApiResponse();
var service = await GetAllOpportunityService(result);
var claimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
var controller = new AllOpportunitiesController(service, claimsPrincipalAccessor,
Mock.Of<IFeatureFlagServiceClient>(),
Mock.Of<ILogger<AllOpportunitiesController>>());
var firstRow = result.Data.First(row => row.OpportunityType == (int)OpportunityTypes.Variation);
await controller.SetHiddenAsync(firstRow.OpportunityGuid, !firstRow.IsHidden, CancellationToken.None);
}
public async Task ExcelExportText()
{
var result = await GetPagedApiResponse();
var service = await GetAllOpportunityService(result);
var claimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
var controller = new AllOpportunitiesController(service, claimsPrincipalAccessor,
Mock.Of<IFeatureFlagServiceClient>(),
Mock.Of<ILogger<AllOpportunitiesController>>());
var export = await controller.Export(new AllOpportunityExportOptions
{
SortOptions = new SortOptions(),
Filters = new AllFilters()
}, CancellationToken.None);
Assert.That(export, Is.AssignableFrom<ExcelContentResult>());
var workbook = ((ExcelContentResult)export).Workbook;
var filename = ((ExcelContentResult)export).FileName;
Assert.Multiple(() =>
{
Assert.That(workbook.Worksheets.First().Rows().Count(), Is.GreaterThan(result.Data.Count()));
Assert.That(filename, Is.Not.Null);
Assert.That(filename, Is.Not.Empty);
});
}
private async Task<PagedApiResponse<AllOpportunity>> GetPagedApiResponse(bool withExplorationOpportunities = true)
{
var jeff = new JazzEntityFrameworkFactory();
var dbContext = await jeff.CreateDbContextAsync(nameof(AllOpportunityControllerTests), CancellationToken.None);
var result = new PagedApiResponse<AllOpportunity>
{
Data = withExplorationOpportunities
? dbContext.AllOpportunities
: dbContext.AllOpportunities.Where(opp => opp.OpportunityType != (int)OpportunityTypes.Blank),
PagingInfo = new PagingInfo
{
ResultsPerPage = 20,
TotalNumberOfResults = dbContext.AllOpportunities.Count(),
TotalPages = dbContext.AllOpportunities.Count() / 20,
PageNumber = 1
}
};
return result;
}
private async Task<IAllOpportunityService> GetAllOpportunityService(PagedApiResponse<AllOpportunity> pagedApiResponse = null)
{
var jeff = new JazzEntityFrameworkFactory();
var dbContext = await jeff.CreateDbContextAsync(nameof(AllOpportunityControllerTests), CancellationToken.None);
var service = Mock.Of<IAllOpportunityService>();
Mock.Get(service)
.Setup(s => s.GetPagedAsync(It.IsAny<PagingOptions>(), It.IsAny<IFilters<AllOpportunity>>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(pagedApiResponse ?? await GetPagedApiResponse()));
Mock.Get(service)
.Setup(s => s.NameIsValid(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<Guid?>(), It.IsAny<CancellationToken>()))
.Returns<Guid, int, string, Guid?, CancellationToken>((config, oppType, name, oppGuid, token)
=> Task.FromResult(!dbContext.AllOpportunities.Any(opp
=> opp.ConfigurationGuid == config
&& opp.OpportunityType == oppType
&& opp.OpportunityGuid == oppGuid
&& opp.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase))));
var filterOptions = new AllFilterOptions
{
OpportunityTypes = Enum.GetValues<OpportunityTypes>()
.Select(ot => new FilterMember { Id = $"{(int)ot}", Text = ot.OpportunityTypeName() })
};
Mock.Get(service)
.Setup(s => s.GetFilterOptionsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>((config, token) => Task.FromResult(filterOptions));
var exportOptions = new ExportOptions() { EmptyMessage = "No Opportunities" };
// configure column formats
exportOptions.AddColumnOptions("Identified Savings", "Currency2Dec");
exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\"");
var xlWorkbook = (IXLWorkbook)ExportUtils.CreateExcelWorkbook(dbContext.AllOpportunities, exportOptions);
Mock.Get(service)
.Setup(s => s.ExportAsync(It.IsAny<SortOptions>(), It.IsAny<AllFilters>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(xlWorkbook));
Mock.Get(service)
.Setup(s => s.SetHiddenAsync(It.IsAny<Guid>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
Mock.Get(service)
.Setup(s => s.SetNoteAsync(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
Mock.Get(service)
.Setup(s => s.GetById(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>((oppGuid, token) => Task.FromResult(dbContext.AllOpportunities.FirstOrDefault(opp => opp.OpportunityGuid == oppGuid)));
return service;
}
}
}
@@ -0,0 +1,85 @@
using FluentAssertions;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives;
using Strata.ContinuousImprovement.Biz.Initiatives;
using Strata.ContinuousImprovement.Biz.Mappers;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class ChargeCodeInitiativesControllerTests
{
[TestCaseSource(nameof(DevDbTestCases1))]
[TestCaseSource(nameof(TestCases))]
public async Task Test(ChargeCodeInitiativesController controller, Guid opportunityGuid, Guid expected)
{
var dto = new InitiativeDto
{
OpportunityGuid = opportunityGuid,
TrackingStart = DateTime.Now.ToString("yyyy/MM/dd")
};
var result = await controller.Create(dto, CancellationToken.None);
var actual = result.Value;
actual.Should().Be(expected);
}
public static IEnumerable<TestCaseData> DevDbTestCases1()
{
var foundOpportunityGuid = Guid.NewGuid();
var notFoundOpportunityGuid = Guid.NewGuid();
var newInitiative = Guid.NewGuid();
var stakeholderUsers = Mock.Of<IStakeholderUser>();
var clientClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
var service = Mock.Of<ICCInitiativeService>();
Mock.Get(service)
.Setup(s => s.CreateChargeCodeInitiative(It.IsAny<Guid>(), It.IsAny<Initiative>(),
It.IsIn(new[] { foundOpportunityGuid }.AsEnumerable()), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(newInitiative));
var controller = new ChargeCodeInitiativesController(clientClaimsPrincipalAccessor, service, stakeholderUsers);
var notFoundService = Mock.Of<ICCInitiativeService>();
Mock.Get(notFoundService)
.Setup(s => s.CreateChargeCodeInitiative(It.IsAny<Guid>(), It.IsAny<Initiative>(),
It.IsNotIn(new[] { foundOpportunityGuid }.AsEnumerable()), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(Guid.Empty));
var notFoundController = new ChargeCodeInitiativesController(clientClaimsPrincipalAccessor, notFoundService, stakeholderUsers);
yield return new TestCaseData(controller, foundOpportunityGuid, newInitiative)
.SetName("Dev Db Opportunity Found");
yield return new TestCaseData(controller, notFoundOpportunityGuid, Guid.Empty)
.SetName("Dev Db Opportunity Not Found");
yield return new TestCaseData(notFoundController, notFoundOpportunityGuid, Guid.Empty)
.SetName("Dev Db Opportunity Not Found 1");
}
public static IEnumerable<TestCaseData> TestCases()
{
var foundOpportunityGuid = Guid.NewGuid();
var notFoundOpportunityGuid = Guid.NewGuid();
var stakeholderUsers = Mock.Of<IStakeholderUser>();
var service = Mock.Of<ICCInitiativeService>();
Mock.Get(service)
.Setup(s => s.CreateChargeCodeInitiative(It.IsIn(new[] { new Guid(TestUtilities.ClientDbGuid) }.AsEnumerable()),
It.IsAny<Initiative>(), It.IsIn(new[] { foundOpportunityGuid }.AsEnumerable()), It.IsAny<CancellationToken>()))
.Throws<Exception>();
var clientClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor(isClientDatabase: true);
var controller = new ChargeCodeInitiativesController(clientClaimsPrincipalAccessor, service, stakeholderUsers);
yield return new TestCaseData(controller, foundOpportunityGuid, Guid.Empty)
.SetName("Client Db Opportunity Found");
yield return new TestCaseData(controller, notFoundOpportunityGuid, Guid.Empty)
.SetName("Client Db Opportunity Not Found");
}
}
}
@@ -0,0 +1,374 @@
using ClosedXML.Excel;
using DocumentFormat.OpenXml.Drawing.Diagrams;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.All.Opportunities;
using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives;
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
using Strata.ContinuousImprovement.Biz.Configuration;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.Dtos;
using Strata.ContinuousImprovement.Biz.Initiatives;
using Strata.ContinuousImprovement.Biz.OrgDefined;
using Strata.ContinuousImprovement.Biz.Shared;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub.Generics;
using Strata.CoreLib.Claims;
using Strata.FeatureFlags.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnitSetup = NUnit.Framework.SetUpAttribute;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class ChargeCodeOpportunitiesControllerTests
{
public JazzDbContext DbContext { get; set; }
public ChargeCodeOpportunitiesController Controller { get; set; }
public IEnumerable<Biz.Configuration.Configuration> Configurations { get; set; }
public IClaimsPrincipalAccessor ClaimsPrincipalAccessor { get; set; }
public IConfigurationService ConfigurationService { get; set; }
public ICCOpportunityService OpportunityService;
public ICCInitiativeService InitiativeService;
public IOpportunityMetricCohortService OpportunityMetricCohortService;
public IFeatureFlagServiceClient FeatureFlagServiceClient;
public IAllOpportunityService AllOpportunityService;
[OneTimeSetUp]
public async Task ConfigurationsControllerTestSetup()
{
// Arrange
var jeff = new JazzEntityFrameworkFactory();
DbContext = await jeff.CreateDbContextAsync(nameof(ConfigurationsControllerTests), CancellationToken.None);
Configurations = jeff.Configurations.ToList();
var configuration = jeff.Configurations.First(c => c.ConfigurationGuid == JazzEntityFrameworkFactory.CurrentConfigurationGuid);
var ccOpportunityDataModel = jeff.CCOpportunities[0].MapToDataModel(configuration);
var workbook = new XLWorkbook();
// Act
ClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
FeatureFlagServiceClient = TestUtilities.GetFeatureFlagServiceClient("chargecodebeta", true);
ConfigurationService = Mock.Of<IConfigurationService>();
Mock.Get(ConfigurationService)
.Setup(s => s.GetConfigurationsAsync(It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(Configurations.AsEnumerable()));
Mock.Get(ConfigurationService)
.Setup(s => s.GetConfigurationAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(configuration));
AllOpportunityService = Mock.Of<IAllOpportunityService>();
Mock.Get(AllOpportunityService)
.Setup(s => s.NameIsValid(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<Guid?>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
OpportunityService = Mock.Of<ICCOpportunityService>();
Mock.Get(OpportunityService)
.Setup(s => s.UpdateAsync(It.IsAny<Guid>(), It.IsAny<CCOpportunityDataModel>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(Guid.NewGuid().ToString()));
Mock.Get(OpportunityService)
.Setup(s => s.ValidateChargeCodesAsync(It.IsAny<List<string>>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
Mock.Get(OpportunityService)
.Setup(s => s.ValidateOpportunityLevelsAsync(It.IsAny<OpportunityLevel>(), It.IsAny<List<string>>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
Mock.Get(OpportunityService)
.Setup(s => s.DeleteAsync<CCOpportunityDataModel>(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
Mock.Get(OpportunityService)
.Setup(s => s.GetById(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>((oppGuid, token)
=> oppGuid == ccOpportunityDataModel.OpportunityGuid
? Task.FromResult(ccOpportunityDataModel)
: Task.FromResult((CCOpportunityDataModel)null));
Mock.Get(OpportunityService)
.Setup(s => s.ExportAsync(It.IsAny<Guid>(), It.IsAny<IEnumerable<string>>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns<Guid, IEnumerable<string>, string, CancellationToken>((oppGuid, chargeCodes, userName, token)
=> oppGuid == ccOpportunityDataModel.OpportunityGuid
? Task.FromResult(((IXLWorkbook)workbook))
: Task.FromResult(((IXLWorkbook)null)));
Mock.Get(OpportunityService)
.Setup(s => s.UpdateGoalTotalUnitsOfServiceAsync(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<double>(), It.IsAny<CancellationToken>()))
.Returns<Guid, string, double, CancellationToken>((oppGuid, key, value, token)
=> oppGuid == ccOpportunityDataModel.OpportunityGuid
? Task.FromResult(true)
: Task.FromResult(false));
Mock.Get(OpportunityService)
.Setup(s => s.UpdateGoalCostPerUnitAsync(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<double>(), It.IsAny<CancellationToken>()))
.Returns<Guid, string, double, CancellationToken>((oppGuid, key, goalValue, token)
=> oppGuid == ccOpportunityDataModel.OpportunityGuid
? Task.FromResult(true)
: Task.FromResult(false));
Mock.Get(OpportunityService)
.Setup(s => s.GetInitiativeInfoAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(new InitiativeInfo()));
Mock.Get(OpportunityService)
.Setup(s => s.RefreshOpportunityTrackingData(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, Guid, CancellationToken>((dbGuid, oppGuid, token)
=> dbGuid.ToString() == TestUtilities.DbGuid && oppGuid == ccOpportunityDataModel.OpportunityGuid
? Task.FromResult("Good")
: Task.FromResult(""));
InitiativeService = Mock.Of<ICCInitiativeService>();
Mock.Get(InitiativeService)
.Setup(s => s.UpdateSavings(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
OpportunityMetricCohortService = Mock.Of<IOpportunityMetricCohortService>();
}
[NUnitSetup]
public void TestSetup()
{
// Arrange
Controller = new ChargeCodeOpportunitiesController(ClaimsPrincipalAccessor, ConfigurationService, OpportunityService, InitiativeService, OpportunityMetricCohortService, FeatureFlagServiceClient, AllOpportunityService);
}
[Test()]
public void ChargeCodeOpportunitiesControllerTest()
{
// Act
var controller = new ChargeCodeOpportunitiesController(ClaimsPrincipalAccessor, ConfigurationService, OpportunityService, InitiativeService, OpportunityMetricCohortService, FeatureFlagServiceClient, AllOpportunityService);
// Assert
controller.Should().NotBeNull();
}
[Test()]
public async Task UpdateOpportunityTest()
{
// Arrange
var opp = await DbContext.ChargeCodeOpportunities.FirstOrDefaultAsync(CancellationToken.None);
var opportunity = opp.MapToSaveModel();
// Act
var result = await Controller.UpdateOpportunity(opportunity.OpportunityGuid, opportunity, CancellationToken.None);
// Assert
var actual = result.Result;
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = (OkObjectResult)result.Result;
okResult.Value.Should().BeOfType<string>();
var value = new Guid(okResult.Value as string);
value.Should().NotBeEmpty();
}
[Test()]
public async Task ValidateChargeCodesAsyncTest()
{
// Arrange
var chargeCodes = new List<string>();
// Act
var result = await Controller.ValidateChargeCodesAsync(new ChargeCodeOpportunitiesController.ValidateChargeCodeDto() { ChargeCodes = chargeCodes }, CancellationToken.None);
// Assert
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = (OkObjectResult)result.Result;
okResult.Value.Should().BeOfType<bool>();
var value = (bool)okResult.Value;
value.Should().BeTrue();
}
[Test()]
public async Task GetCustomPickerChargeCodesAsyncTest()
{
// Arrange
var chargeCodes = new List<string>();
// Act
var result = await Controller.GetChargeCodeMembers(chargeCodes, CancellationToken.None);
// Assert
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = (OkObjectResult)result.Result;
okResult.Value.Should().BeOfType<ChargeCodePickerModel[]>();
var value = (ChargeCodePickerModel[])okResult.Value;
value.Should().NotBeNull();
}
[Test()]
public async Task ValidateOpportunityLevelsAsyncTest()
{
// Arrange
var info = new ChargeCodeOpportunitiesController.OpportunityLevelSelectionInfo
{
Configuration = Configurations.First().ConfigurationGuid,
OpportunityLevel = OpportunityLevel.Entity,
OpportunityLevels = new List<string>()
};
// Act
var result = await Controller.ValidateOpportunityLevelsAsync(info, CancellationToken.None);
// Assert
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = (OkObjectResult)result.Result;
okResult.Value.Should().BeOfType<bool>();
var value = (bool)okResult.Value;
value.Should().BeTrue();
}
[Test()]
public async Task DeleteTest()
{
// Act
var result = await Controller.Delete(Guid.NewGuid(), CancellationToken.None);
// Assert
result.Should().BeTrue();
}
[TestCase(true, TestName = "GetByIdTest - true")]
[TestCase(false, TestName = "GetByIdTest - false")]
public async Task GetByIdTest(bool withOpp)
{
// Arrange
var opp = await DbContext.ChargeCodeOpportunities.FirstAsync(CancellationToken.None);
// Act
var result = await Controller.GetById(withOpp ? opp.OpportunityGuid : Guid.NewGuid(), CancellationToken.None);
// Assert
if (withOpp)
{
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = (OkObjectResult)result.Result;
okResult.Value.Should().BeOfType<CCOpportunityDto>()
.And.NotBeOfType<CCOpportunityDataModel>();
}
else
{
result.Result.Should().BeOfType<NotFoundResult>();
}
}
[TestCase(true, TestName = "GetEditModelTest - true")]
[TestCase(false, TestName = "GetEditModelTest - false")]
public async Task GetEditModelTest(bool withOpp)
{
// Arrange
var opp = await DbContext.ChargeCodeOpportunities.FirstAsync(CancellationToken.None);
// Act
var result = await Controller.GetEditModel(withOpp ? opp.OpportunityGuid : Guid.NewGuid(), CancellationToken.None);
// Assert
if (withOpp)
{
result.Should().BeOfType<CCOpportunitySaveModel>();
}
else
{
result.Should().BeNull();
}
}
[TestCase(true, TestName = "UpdateGoalTotalUnitsOfServiceTest - true")]
[TestCase(false, TestName = "UpdateGoalTotalUnitsOfServiceTest - false")]
public async Task UpdateGoalTotalUnitsOfServiceTest(bool withOpp)
{
// Arrange
var opp = await DbContext.ChargeCodeOpportunities.FirstAsync(CancellationToken.None);
// Act
var result = await Controller.UpdateGoalTotalUnitsOfService(withOpp ? opp.OpportunityGuid : Guid.NewGuid(), new ChargeCodeOpportunitiesController.GoalSetDto(), CancellationToken.None);
// Assert
result.Should().Be(withOpp);
}
[TestCase(true, TestName = "UpdateGoalCostPerUnitTest - true")]
[TestCase(false, TestName = "UpdateGoalCostPerUnitTest - false")]
public async Task UpdateGoalCostPerUnitTest(bool withOpp)
{
// Arrange
var opp = await DbContext.ChargeCodeOpportunities.FirstAsync(CancellationToken.None);
// Act
var result = await Controller.UpdateGoalCostPerUnit(withOpp ? opp.OpportunityGuid : Guid.NewGuid(), new ChargeCodeOpportunitiesController.GoalSetDto(), CancellationToken.None);
// Assert
result.Should().Be(withOpp);
}
[TestCase(true, TestName = "ExportTest - true")]
[TestCase(false, TestName = "ExportTest - false")]
public async Task ExportTest(bool withOpp)
{
// Arrange
var opp = await DbContext.ChargeCodeOpportunities.FirstAsync(CancellationToken.None);
var chargeCodeItems = new ChargeCodeOpportunitiesController.ChargeCodeItems
{
ChargeCodes = new List<string>()
};
// Act
var result = await Controller.Export(withOpp ? opp.OpportunityGuid : Guid.NewGuid(), chargeCodeItems, CancellationToken.None);
// Assert
result.Should().BeOfType<ExcelContentResult>();
var excelContentResult = (ExcelContentResult)result;
if (withOpp)
{
excelContentResult.Workbook.Should().NotBeNull();
}
else
{
excelContentResult.Workbook.Should().BeNull();
}
}
[Test()]
public async Task OpportunityCreateEnabledTest()
{
// Act
var result = await Controller.OpportunityCreateEnabled(CancellationToken.None);
// Assert
result.Should().BeTrue();
}
[Test()]
public async Task GetInitiativeInfoTest()
{
// Act
var result = await Controller.GetInitiativeInfo(Guid.NewGuid(), CancellationToken.None);
// Assert
result.Should().BeOfType<InitiativeInfo>()
.And.NotBeNull();
}
[TestCase(true, TestName = "RefreshTest - true")]
[TestCase(false, TestName = "RefreshTest - false")]
public async Task RefreshTest(bool withOpp)
{
// Arrange
var opp = await DbContext.ChargeCodeOpportunities.FirstAsync(CancellationToken.None);
// Act
var result = await Controller.Refresh(new ChargeCodeOpportunitiesController.RefreshData
{
DatabaseGuid = new Guid(TestUtilities.DbGuid),
Guid = withOpp ? opp.OpportunityGuid : Guid.NewGuid()
}, CancellationToken.None);
// Assert
result.Should().BeOfType<string>();
if (withOpp)
{
result.Should().NotBeNullOrEmpty();
}
else
{
result.Should().BeNullOrEmpty();
}
}
}
}
@@ -0,0 +1,127 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Api.Test.Unit.Generics;
using Strata.ContinuousImprovement.Biz.ChargeCode.Jobs;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class ChargeCodeOpportunityDetailsControllerTests
{
public ChargeCodeOpportunityDetailsController Controller { get; set; }
public FakeBackgroundJobClient BackgroundJobClient { get; set; }
private Mock<ICCOpportunityClientJob> _mockClientJob;
private ILogger<ChargeCodeOpportunityDetailsController> _logger;
[OneTimeSetUp]
public void TestSetup()
{
// Arrange
BackgroundJobClient ??= new FakeBackgroundJobClient();
_mockClientJob = new Mock<ICCOpportunityClientJob>();
_logger = Mock.Of<ILogger<ChargeCodeOpportunityDetailsController>>();
_mockClientJob
.Setup(j => j.RefreshClientOpportunityBaselineDetails(It.IsAny<int>(), It.Is<Guid>(g => g != Guid.Empty), It.IsAny<CancellationToken>()))
.ReturnsAsync("baseline-job-123");
_mockClientJob
.Setup(j => j.RefreshClientOpportunityTrackingDetails(It.IsAny<int>(), It.Is<Guid>(g => g != Guid.Empty), It.IsAny<CancellationToken>()))
.ReturnsAsync("tracking-job-456");
_mockClientJob
.Setup(j => j.RefreshClientOpportunityBaselineDetails(It.IsAny<int>(), Guid.Empty, It.IsAny<CancellationToken>()))
.ThrowsAsync(new InvalidOperationException("Opportunity missing"));
_mockClientJob
.Setup(j => j.RefreshClientOpportunityTrackingDetails(It.IsAny<int>(), Guid.Empty, It.IsAny<CancellationToken>()))
.ThrowsAsync(new InvalidOperationException("Initiative missing"));
Controller = new ChargeCodeOpportunityDetailsController(BackgroundJobClient, _mockClientJob.Object, _logger);
}
[Test]
public void ChargeCodeOpportunityDetailsControllerTest()
{
// Arrange
var controller = new ChargeCodeOpportunityDetailsController(BackgroundJobClient, _mockClientJob.Object, _logger);
// Assert
controller.Should().NotBeNull();
}
[Test]
public void CreateTest()
{
// Arrange
Action create = () => Controller.Create(new ChargeCodeOpportunityDetailsController.ChargeCodeDetailInfo
{
StrataId = 9,
OpportunityGuid = Guid.NewGuid(),
IsBaseline = true,
IsUpdate = true
}, CancellationToken.None);
// Assert
create.Should().NotThrow();
}
[Test]
public async Task RefreshBaselineDetails_ReturnsOk_OnSuccess()
{
var request = new ChargeCodeOpportunityDetailsController.ChargeCodeOpportunityJobRequestInfo
{
StrataId = 9,
OpportunityGuid = Guid.NewGuid()
};
var result = await Controller.RefreshBaselineDetails(request, CancellationToken.None);
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
ok.Value.Should().Be("baseline-job-123");
}
[Test]
public async Task RefreshTrackingDetails_ReturnsOk_OnSuccess()
{
var request = new ChargeCodeOpportunityDetailsController.ChargeCodeOpportunityJobRequestInfo
{
StrataId = 9,
OpportunityGuid = Guid.NewGuid()
};
var result = await Controller.RefreshTrackingDetails(request, CancellationToken.None);
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
ok.Value.Should().Be("tracking-job-456");
}
[Test]
public async Task RefreshBaselineDetails_ReturnsBadRequest_OnException()
{
var request = new ChargeCodeOpportunityDetailsController.ChargeCodeOpportunityJobRequestInfo
{
StrataId = 9,
OpportunityGuid = Guid.Empty
};
var result = await Controller.RefreshBaselineDetails(request, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
}
[Test]
public async Task RefreshTrackingDetails_ReturnsBadRequest_OnException()
{
var request = new ChargeCodeOpportunityDetailsController.ChargeCodeOpportunityJobRequestInfo
{
StrataId = 9,
OpportunityGuid = Guid.Empty
};
var result = await Controller.RefreshTrackingDetails(request, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
}
}
}
@@ -0,0 +1,218 @@
using ClosedXML.Excel;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Moq;
using NUnit.Framework;
using Strata.ApiLib.Standard.Models;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives;
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters;
using Strata.ContinuousImprovement.Biz.Filter;
using Strata.ContinuousImprovement.Biz.OpportunityComments;
using Strata.ContinuousImprovement.Biz.Pagination;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnitSetup = NUnit.Framework.SetUpAttribute;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class ChargeCodeOppsControllerTests
{
public JazzDbContext DbContext { get; set; }
public ChargeCodeOppsController Controller { get; set; }
public ICommentService CommentService { get; set; }
public ICCOpportunityViewService OpportunityService { get; set; }
public ICCInitiativeService InitiativeService { get; set; }
[OneTimeSetUp]
public async Task ConfigurationsControllerTestSetup()
{
// Arrange
var jeff = new JazzEntityFrameworkFactory();
DbContext = await jeff.CreateDbContextAsync(nameof(ChargeCodeOppsControllerTests), CancellationToken.None);
var expectedData = jeff.CCOpportunities;
var expected = expectedData[0];
var expectedage = new PagedApiResponse<CCOpportunity>
{
Data = expectedData,
PagingInfo = new PagingInfo
{
TotalNumberOfResults = expectedData.Count,
TotalPages = 1,
ResultsPerPage = expectedData.Count,
PageNumber = 1
}
};
var workbook = new XLWorkbook();
workbook.AddWorksheet("ChargeCodeOpps");
CommentService = Mock.Of<ICommentService>();
OpportunityService = Mock.Of<ICCOpportunityViewService>();
Mock.Get(OpportunityService)
.Setup(s => s.GetPagedAsync(It.IsAny<PagingOptions>(), It.IsAny<IFilters<CCOpportunity>>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(expectedage));
Mock.Get(OpportunityService)
.Setup(s => s.ExportAsync(It.IsAny<SortOptions>(), It.IsAny<CCFilters>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult((IXLWorkbook)workbook));
Mock.Get(OpportunityService)
.Setup(s => s.GetFilterOptionsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(new CCFilterOptions()));
Mock.Get(OpportunityService)
.Setup(s => s.GetById(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>((oppGuid, token)
=> oppGuid == expected.OpportunityGuid
? Task.FromResult(expected)
: Task.FromResult((CCOpportunity)null));
var hiddenExpected = expected.IsHidden = !expectedData[0].IsHidden;
Mock.Get(OpportunityService)
.Setup(s => s.SetHiddenAsync(It.IsAny<Guid>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.Returns<Guid, bool, CancellationToken>((oppGuid, isHidden, token)
=> oppGuid == expected.OpportunityGuid
? Task.CompletedTask
: Task.FromResult((CCOpportunity)null));
Mock.Get(OpportunityService)
.Setup(s => s.SetNoteAsync(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns<Guid, string, CancellationToken>((oppGuid, note, token)
=> oppGuid == expected.OpportunityGuid
? Task.CompletedTask
: Task.FromResult((CCOpportunity)null));
InitiativeService = Mock.Of<ICCInitiativeService>();
}
[NUnitSetup]
public void TestSetup()
{
// Arrange
Controller = new ChargeCodeOppsController(OpportunityService);
}
[Test()]
public void ChargeCodeOppsControllerTest()
{
Controller.Should().NotBeNull();
}
[Test()]
public async Task GetOpportunitiesTest()
{
// Arrange
var loadOptions = new ChargeCodeOppsController.CCOpportunityLoadOptions
{
PagingOptions = new PagingOptions
{
First = 1,
Rows = int.MaxValue,
SortField = "IdentifiedSavings",
SortOrder = SortOrder.Desc
},
Filters = new CCFilters
{
ConfigurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid
}
};
// Act
var result = await Controller.GetOpportunities(loadOptions, CancellationToken.None);
// Assert
result.Should().BeOfType<PagedApiResponse<CCOpportunity>>();
result.PagingInfo.Should().NotBeNull();
result.Data.Should().NotBeNull();
result.Data.Count().Should().Be(result.PagingInfo.TotalNumberOfResults);
}
[Test()]
public async Task ExportTest()
{
// Arrange
var exportOptions = new ChargeCodeOppsController.CCOpportunityExportOptions
{
Filters = new CCFilters
{
ConfigurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid
}
};
// Act
var result = await Controller.Export(exportOptions, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Should().BeOfType<ExcelContentResult>();
var excelContentResult = result as ExcelContentResult;
excelContentResult.Should().NotBeNull();
excelContentResult.FileName.Should().NotBeNullOrEmpty();
excelContentResult.Workbook.Should().NotBeNull();
}
[Test()]
public async Task GetFilterOptionsAsyncTest()
{
// Act
var result = await Controller.GetFilterOptionsAsync(JazzEntityFrameworkFactory.CurrentConfigurationGuid, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Should().BeOfType<CCFilterOptions>();
}
// commenting out because we don't have getById endpoint test should be redone
[TestCase(true, TestName = "GetByIdTest - true", Ignore = "When I debug it passes, otherwise it doesn't")]
[TestCase(false, TestName = "GetByIdTest - false")]
public async Task GetByIdTest(bool withOpp)
{
//// Arrange
//var opp = await DbContext.ChargeCodeOpps.FirstOrDefaultAsync(CancellationToken.None);
//// Act
////var result = await Controller.GetById(withOpp ? opp.OpportunityGuid : Guid.NewGuid(), CancellationToken.None);
//// Assert
//if (withOpp)
//{
// result.Should().NotBeNull();
//}
//else
//{
// result.Should().BeNull();
//}
}
[TestCase(true, true, TestName = "SetHiddenAsyncTest - true - isHidden")]
[TestCase(true, false, TestName = "SetHiddenAsyncTest - true - not isHidden")]
[TestCase(false, true, TestName = "SetHiddenAsyncTest - false - isHidden")]
[TestCase(false, false, TestName = "SetHiddenAsyncTest - false - not isHidden")]
public async Task SetHiddenAsyncTest(bool withOpp, bool isHidden)
{
// Arrange
var opp = await DbContext.ChargeCodeOpps.FirstOrDefaultAsync(CancellationToken.None);
var opportunityGuid = withOpp ? opp.OpportunityGuid : Guid.NewGuid();
// Assert
Assert.DoesNotThrowAsync(async () => // Act
await Controller.SetHiddenAsync(opportunityGuid, isHidden, CancellationToken.None));
}
[TestCase(true, "", TestName = "SetNoteAsyncTest - true - note")]
[TestCase(true, null, TestName = "SetNoteAsyncTest - true - null")]
[TestCase(false, "", TestName = "SetNoteAsyncTest - false - not")]
[TestCase(false, null, TestName = "SetNoteAsyncTest - false - null")]
public async Task SetNoteAsyncTest(bool withOpp, string note)
{
// Arrange
var opp = await DbContext.ChargeCodeOpps.FirstOrDefaultAsync(CancellationToken.None);
var opportunityGuid = withOpp ? opp.OpportunityGuid : Guid.NewGuid();
// Assert
Assert.DoesNotThrowAsync(async () => // Act
await Controller.SetNoteAsync(opportunityGuid, note, CancellationToken.None));
}
}
}
@@ -0,0 +1,377 @@
using DocumentFormat.OpenXml.Drawing.Diagrams;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Strata.ApiLib.Standard.Models;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Api.Test.Unit.Generics;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.Dtos;
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters;
using Strata.ContinuousImprovement.Biz.Network;
using Strata.ContinuousImprovement.Biz.Pagination;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.CoreLib.Claims;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnitSetup = NUnit.Framework.SetUpAttribute;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class ClientNetworkOpportunitiesControllerTests
{
public CentralDbContext DbContext { get; set; }
public ClientNetworkOpportunitiesController Controller { get; set; }
public ClientNetworkOpportunitiesController EmptyDbController { get; set; }
public ClientNetworkOpportunitiesController ControllerExceptions { get; set; }
public IClaimsPrincipalAccessor ClaimsPrincipalAccessor { get; set; }
public IClaimsPrincipalAccessor EnptyDbClaimsPrincipalAccessor { get; set; }
public IClientNetworkOpportunityService ClientNetworkOpportunityService { get; set; }
public IClientNetworkOpportunityService ClientNetworkOpportunityServiceException { get; set; }
public ILogger<ClientNetworkOpportunitiesController> Logger { get; set; }
[OneTimeSetUp]
public async Task ClientNetworkOpportunitiesControllerTestsSetup()
{
// Arrange
var jeff = new JazzEntityFrameworkFactory();
DbContext = await jeff.CreateCentralDbContextAsync(nameof(ClientNetworkOpportunitiesControllerTests), CancellationToken.None);
var expectedData = jeff.ClientNetworkOpportunities;
var opportunity = expectedData[0];
var expected = new PagedApiResponse<NetworkOpportunitySummary>
{
Data = expectedData.Select(d => new NetworkOpportunitySummary
{
NetworkOpportunityId = d.NetworkOpportunityId,
Name = d.NetworkOpportunity.Name,
AnalyticsWorkbookId = d.AnalyticsWorkbookId,
CostDriver = d.NetworkOpportunity.CostDriver,
Description = d.NetworkOpportunity.Description,
IsPublished = d.IsPublished
}),
PagingInfo = new PagingInfo
{
PageNumber = 1,
TotalNumberOfResults = expectedData.Count,
TotalPages = 1,
ResultsPerPage = expectedData.Count
}
};
// Act
ClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
EnptyDbClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor(isEmptyDatabase: true);
ClientNetworkOpportunityService = Mock.Of<IClientNetworkOpportunityService>();
Mock.Get(ClientNetworkOpportunityService)
.Setup(s => s.GetOpportunitiesAsync(It.IsAny<Guid>(), It.IsAny<PagingOptions>(), It.IsAny<NetworkOpportunityFilters>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(expected));
Mock.Get(ClientNetworkOpportunityService)
.Setup(s => s.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.Returns<Guid, int, CancellationToken>((dbGuid, networkOpportunityId, token) =>
networkOpportunityId == opportunity.NetworkOpportunityId
? Task.FromResult(opportunity)
: Task.FromResult((ClientNetworkOpportunity)null));
Mock.Get(ClientNetworkOpportunityService)
.Setup(s => s.ConfigureAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<ClientNetworkOpportunity>(), It.IsAny<NetworkOpportunitySaveModel>(), It.IsAny<CancellationToken>()));
ClientNetworkOpportunityServiceException = Mock.Of<IClientNetworkOpportunityService>();
Mock.Get(ClientNetworkOpportunityServiceException)
.Setup(s => s.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.Throws(new CreateWorkbookFromTemplateException());
Logger = Mock.Of<ILogger<ClientNetworkOpportunitiesController>>();
}
[NUnitSetup]
public void TestSetup()
{
Controller = new ClientNetworkOpportunitiesController(ClaimsPrincipalAccessor, ClientNetworkOpportunityService, Logger);
EmptyDbController = new ClientNetworkOpportunitiesController(EnptyDbClaimsPrincipalAccessor, ClientNetworkOpportunityService, Logger);
ControllerExceptions = new ClientNetworkOpportunitiesController(ClaimsPrincipalAccessor, ClientNetworkOpportunityServiceException, Logger);
}
[Test()]
public async Task ClientNetworkOpportunitiesControllerTest()
{
// Assert
Controller.Should().NotBeNull();
}
[Test()]
public async Task GetOpportunitiesTest()
{
// Arrange
var loadOptions = new NetworkOpportunityLoadOptions
{
Filters = new NetworkOpportunityFilters
{
},
PagingOptions = new Biz.Pagination.PagingOptions
{
First = 0,
Rows = 24
}
};
// Act
var result = await Controller.GetOpportunities(loadOptions, CancellationToken.None);
result.Should().NotBeNull();
result.Value.Should().NotBeNull()
.And.BeOfType<PagedApiResponse<NetworkOpportunitySummary>>();
}
[TestCase(true, TestName = "GetByIdTest w/ Exception")]
[TestCase(false, TestName = "GetByIdTest w/o Exception")]
public async Task GetByIdTest(bool withException)
{
// Arrange
var opportunity = DbContext.NetworkOpportunities.FirstOrDefault();
var clientNetworkOpportunity = DbContext.ClientNetworkOpportunities.FirstOrDefault(cno => cno.NetworkOpportunityId == opportunity.NetworkOpportunityId);
var dto = clientNetworkOpportunity.MapToDto();
ActionResult<ClientNetworkOpportunityDto> result;
// Act
if (withException)
{
result = await ControllerExceptions.GetById(opportunity.NetworkOpportunityId, CancellationToken.None);
}
else
{
result = await Controller.GetById(opportunity.NetworkOpportunityId, CancellationToken.None);
}
// Assert
if (withException)
{
result.Value.Should().BeNull();
result.Result.Should().BeOfType<ObjectResult>();
var objectResult = (ObjectResult)result.Result;
objectResult.StatusCode.Should().Be(500);
}
else
{
result.Value.Should().NotBeNull()
.And.BeOfType<ClientNetworkOpportunityDto>()
.And.BeEquivalentTo(dto);
}
}
[TestCase(true, TestName = "SaveTest w/ Exception")]
[TestCase(false, TestName = "SaveTest w/o Exception")]
public async Task SaveTest(bool withException)
{
// Arrange
var opportunity = DbContext.NetworkOpportunities.FirstOrDefault();
var clientNetworkOpportunity = DbContext.ClientNetworkOpportunities.FirstOrDefault(cno => cno.NetworkOpportunityId == opportunity.NetworkOpportunityId);
var dto = clientNetworkOpportunity.MapToDto();
// Act
IActionResult result;
if (withException)
{
result = await ControllerExceptions.Save(-1, dto, CancellationToken.None);
}
else
{
result = await Controller.Save(opportunity.NetworkOpportunityId, dto, CancellationToken.None);
}
// Assert
if (withException)
{
result.Should().NotBeNull()
.And.BeOfType<BadRequestResult>();
}
else
{
result.Should().NotBeNull()
.And.BeOfType<NoContentResult>();
}
}
[Test()]
public async Task DeleteTest()
{
// Arrange
var opportunity = DbContext.NetworkOpportunities.FirstOrDefault();
// Act
IActionResult result;
result = await Controller.Delete(opportunity.NetworkOpportunityId, CancellationToken.None);
// Assert
result.Should().NotBeNull()
.And.BeOfType<NoContentResult>();
}
[TestCase(true, true, TestName = "PublishTest w/ Exception on EmptyDb")]
[TestCase(false, true, TestName = "PublishTest w/o Exception on EmptyDb")]
[TestCase(true, false, TestName = "PublishTest w/ Exception")]
[TestCase(false, false, TestName = "PublishTest w/o Exception")]
public async Task PublishTest(bool withException, bool withEmptyDb)
{
// Arrange
var opportunity = DbContext.NetworkOpportunities.FirstOrDefault();
var clientNetworkOpportunity = DbContext.ClientNetworkOpportunities.FirstOrDefault(cno => cno.NetworkOpportunityId == opportunity.NetworkOpportunityId);
var dto = clientNetworkOpportunity.MapToDto();
ActionResult<bool> result;
// Act
if (withEmptyDb)
{
if (withException)
{
result = await EmptyDbController.Publish(-1, true, CancellationToken.None);
}
else
{
result = await EmptyDbController.Publish(opportunity.NetworkOpportunityId, true, CancellationToken.None);
}
}
else
{
if (withException)
{
result = await Controller.Publish(-1, true, CancellationToken.None);
}
else
{
result = await Controller.Publish(opportunity.NetworkOpportunityId, true, CancellationToken.None);
}
}
// Assert
if (withEmptyDb)
{
if (withException)
{
result.Should().NotBeNull();
result.Should().BeOfType<ActionResult<bool>>();
result.Result.Should().BeOfType<BadRequestResult>();
result.Value.Should().BeFalse();
}
else
{
result.Should().NotBeNull();
result.Should().BeOfType<ActionResult<bool>>();
result.Result.Should().BeOfType<BadRequestResult>();
}
}
else
{
if (withException)
{
result.Should().NotBeNull();
result.Should().BeOfType<ActionResult<bool>>();
result.Result.Should().BeOfType<BadRequestResult>();
result.Value.Should().BeFalse();
}
else
{
result.Should().NotBeNull();
result.Should().BeOfType<ActionResult<bool>>();
result.Result.Should().BeOfType<OkObjectResult>();
}
}
}
[Test()]
public async Task UpdateInitiativeStatusTest()
{
// Arrange
var opportunity = DbContext.NetworkOpportunities.FirstOrDefault();
var clientNetworkOpportunity = DbContext.ClientNetworkOpportunities.FirstOrDefault(cno => cno.NetworkOpportunityId == opportunity.NetworkOpportunityId);
var dto = clientNetworkOpportunity.MapToDto();
// Act
var result = await Controller.UpdateInitiativeStatus(opportunity.NetworkOpportunityId, Biz.Shared.InitiativeStatuses.Completed, CancellationToken.None);
// Assert
result.Should().NotBeNull()
.And.BeOfType<NoContentResult>();
}
[TestCase(true, true, TestName = "RevertWorkbookToTemplateTest w/ Exception on EmptyDb")]
[TestCase(false, true, TestName = "RevertWorkbookToTemplateTest w/o Exception on EmptyDb")]
[TestCase(true, false, TestName = "RevertWorkbookToTemplateTest w/ Exception")]
[TestCase(false, false, TestName = "RevertWorkbookToTemplateTest w/o Exception")]
public async Task RevertWorkbookToTemplateTest(bool withException, bool withEmptyDb)
{
// Arrange
var opportunity = DbContext.NetworkOpportunities.FirstOrDefault();
var clientNetworkOpportunity = DbContext.ClientNetworkOpportunities.FirstOrDefault(cno => cno.NetworkOpportunityId == opportunity.NetworkOpportunityId);
var dto = clientNetworkOpportunity.MapToDto();
ActionResult<bool> result;
// Act
if (withEmptyDb)
{
if (withException)
{
result = await EmptyDbController.RevertWorkbookToTemplate(-1, CancellationToken.None);
}
else
{
result = await EmptyDbController.RevertWorkbookToTemplate(opportunity.NetworkOpportunityId, CancellationToken.None);
}
}
else
{
if (withException)
{
result = await Controller.RevertWorkbookToTemplate(-1, CancellationToken.None);
}
else
{
result = await Controller.RevertWorkbookToTemplate(opportunity.NetworkOpportunityId, CancellationToken.None);
}
}
// Assert
if (withEmptyDb)
{
if (withException)
{
result.Should().NotBeNull();
result.Should().BeOfType<ActionResult<bool>>();
result.Result.Should().BeOfType<BadRequestResult>();
result.Value.Should().BeFalse();
}
else
{
result.Should().NotBeNull();
result.Should().BeOfType<ActionResult<bool>>();
result.Result.Should().BeOfType<BadRequestResult>();
}
}
else
{
if (withException)
{
result.Should().NotBeNull();
result.Should().BeOfType<ActionResult<bool>>();
result.Result.Should().BeOfType<BadRequestResult>();
result.Value.Should().BeFalse();
}
else
{
result.Should().NotBeNull();
result.Should().BeOfType<ActionResult<bool>>();
result.Result.Should().BeOfType<OkObjectResult>();
}
}
}
}
}
@@ -0,0 +1,289 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.All.Opportunities;
using Strata.ContinuousImprovement.Biz.Initiatives;
using Strata.ContinuousImprovement.Biz.Network;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using Strata.CoreLib.Claims;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NUnitSetup = NUnit.Framework.SetUpAttribute;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class ClientNetworkOpportunityLinkControllerTests
{
public ClientNetworkOpportunityLinkController Controller { get; set; }
public ClientNetworkOpportunityLinkController EmptyDbController { get; set; }
public IClaimsPrincipalAccessor ClaimsPrincipalAccessor { get; set; }
public IClaimsPrincipalAccessor EmptyDbClaimsPrincipalAccessor { get; set; }
public IClientNetworkOpportunityService clientNetworkOpportunityService;
public IClientNetworkOpportunityLinkService clientNetworkOpportunityLinkService;
public IAllOpportunityService allOpportunityService;
public IInitiativeService initiativeService;
public ILogger<ClientNetworkOpportunityLinkController> logger;
[OneTimeSetUp]
public void ClientNetworkOpportunityLinkControllerTestsSetup()
{
// Arrange
clientNetworkOpportunityService = Mock.Of<IClientNetworkOpportunityService>();
clientNetworkOpportunityLinkService = Mock.Of<IClientNetworkOpportunityLinkService>();
allOpportunityService = Mock.Of<IAllOpportunityService>();
initiativeService = Mock.Of<IInitiativeService>();
logger = Mock.Of<ILogger<ClientNetworkOpportunityLinkController>>();
// Act
ClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
EmptyDbClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor(isEmptyDatabase: true);
var expected = new ClientNetworkOpportunityLink
{
OpportunityGuid = Guid.NewGuid(),
ConfigurationGuid = Guid.NewGuid(),
ClientNetworkOpportunityLinkId = 1,
InitiativeGuid = Guid.NewGuid(),
ClientNetworkOpportunity = new ClientNetworkOpportunity
{
NetworkOpportunityId = 1,
}
};
Mock.Get(clientNetworkOpportunityLinkService)
.Setup(s => s.RolloverOpportunityLinkAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<Guid>(),
It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, Guid, Guid, Guid, Guid, Guid, CancellationToken>(
(dbGuid, SrcConfigGuid, SrcOppGuid, RollConfigGuid, RollOppGuid, RollInitGuid, token) =>
dbGuid == Guid.Empty || SrcOppGuid == Guid.Empty
? Task.FromResult(default(ClientNetworkOpportunityLink))
: Task.FromResult(expected));
Mock.Get(clientNetworkOpportunityLinkService)
.Setup(s => s.DeleteOpportunityLinkAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<Guid>(),
It.IsAny<CancellationToken>()));
}
[NUnitSetup]
public void TestSetup()
{
// Act
Controller = new ClientNetworkOpportunityLinkController(
ClaimsPrincipalAccessor,
clientNetworkOpportunityService,
clientNetworkOpportunityLinkService,
allOpportunityService,
initiativeService,
logger);
EmptyDbController = new ClientNetworkOpportunityLinkController(
EmptyDbClaimsPrincipalAccessor,
clientNetworkOpportunityService,
clientNetworkOpportunityLinkService,
allOpportunityService,
initiativeService,
logger);
}
[Test()]
public async Task ClientNetworkOpportunityLinkControllerTest()
{
// Assert
Controller.Should().NotBeNull();
EmptyDbController.Should().NotBeNull();
}
[TestCaseSource(nameof(AddOpportunityLinkTestCases))]
public async Task AddOpportunityLinkTest(Guid opportunityGuid)
{
var result = await Controller.AddOpportunityLink(new ClientNetworkOpportunityLinkController.CreateNetworkOpportunityLinkDto
{
OpportunityGuid = opportunityGuid
}, CancellationToken.None);
result.Should().NotBeNull();
result.Should().BeOfType<ActionResult<ClientNetworkOpportunityLinkController.ClientNetworkOpportunityResultDto>>();
result.Result.Should().NotBeNull();
if (opportunityGuid == Guid.Empty)
{
result.Result.Should().BeOfType<BadRequestResult>();
}
else
{
result.Should().NotBeOfType<BadRequestResult>();
}
}
public static IEnumerable<TestCaseData> AddOpportunityLinkTestCases()
{
yield return new TestCaseData(Guid.Empty)
.SetName(nameof(AddOpportunityLinkTestCases) + " w/Empty Opp Guid");
yield return new TestCaseData(Guid.NewGuid())
.SetName(nameof(AddOpportunityLinkTestCases) + " w/Opp Guid");
}
[TestCaseSource(nameof(RolloverOpportunityLinkTestCases))]
public async Task RolloverOpportunityLinkTest(Guid srcOppGuid, bool useEmptyController)
{
// Arrange
var rolloverOpportunityInfo = new ClientNetworkOpportunityLinkController.RolloverOpportunityDto
{
SourceOpportunityGuid = srcOppGuid,
SourceConfigurationGuid = Guid.Empty,
DatabaseGuid = useEmptyController ? Guid.Empty : new Guid(TestUtilities.DbGuid),
};
// Act
var result = await Controller.RolloverOpportunityLink(rolloverOpportunityInfo, CancellationToken.None);
// Assert
result.Should().NotBeNull();
if (useEmptyController)
{
result.Result.Should().BeOfType<BadRequestResult>();
}
else
{
result.Result.Should().BeOfType<OkObjectResult>();
}
}
public static IEnumerable<TestCaseData> RolloverOpportunityLinkTestCases()
{
yield return new TestCaseData(Guid.Empty, true)
.SetName(nameof(RolloverOpportunityLinkTest) + " w/Empty Opp Guid, Empty Db Guid");
yield return new TestCaseData(Guid.Empty, false)
.SetName(nameof(RolloverOpportunityLinkTest) + " w/Empty Opp Guid");
yield return new TestCaseData(Guid.NewGuid(), true)
.SetName(nameof(RolloverOpportunityLinkTest) + " w/Opp Guid, Empty Db Guid");
yield return new TestCaseData(Guid.NewGuid(), false)
.SetName(nameof(RolloverOpportunityLinkTest) + " w/Opp Guid");
}
[TestCaseSource(nameof(DeleteOpportunityLinkTestCases))]
public async Task DeleteOpportunityLinkTest(Guid srcOppGuid, Guid configurationGuid, Guid databaseGuid, bool useEmptyController)
{
// Arrange
Mock.Get(clientNetworkOpportunityLinkService)
.Setup(s => s.ContainsOpportunityLinkAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<Guid>(),
It.IsAny<CancellationToken>()))
.Returns<Guid, Guid, Guid, CancellationToken>(
(dbGuid, configurationGuid, oppGuid, token) =>
oppGuid == Guid.Empty || dbGuid == Guid.Empty
? Task.FromResult(false)
: Task.FromResult(true));
var opportunity = new ClientNetworkOpportunityLinkController.ClientNetworkOpportunityLinkDto
{
ConfigurationGuid = configurationGuid,
DatabaseGuid = databaseGuid,
Id = srcOppGuid
};
// Act
ActionResult<bool> result;
if (useEmptyController)
{
result = await EmptyDbController.DeleteOpportunityLink(opportunity, CancellationToken.None);
}
else
{
result = await Controller.DeleteOpportunityLink(opportunity, CancellationToken.None);
}
// Assert
result.Should().NotBeNull();
if (useEmptyController)
{
result.Result.Should().BeOfType<BadRequestResult>();
}
}
public static IEnumerable<TestCaseData> DeleteOpportunityLinkTestCases()
{
var configurationGuid = JazzEntityFrameworkStub.JazzEntityFrameworkFactory.CurrentConfigurationGuid;
var databaseGuid = JazzEntityFrameworkStub.JazzEntityFrameworkFactory.DatabaseGuid;
yield return new TestCaseData(Guid.Empty, configurationGuid, Guid.Empty, true)
.SetName(nameof(DeleteOpportunityLinkTest) + " w/Empty Opp Guid, Empty Db Guid");
yield return new TestCaseData(Guid.Empty, configurationGuid, null, true)
.SetName(nameof(DeleteOpportunityLinkTest) + " w/Empty Opp Guid, Null Db Guid");
yield return new TestCaseData(Guid.Empty, configurationGuid, databaseGuid, false)
.SetName(nameof(DeleteOpportunityLinkTest) + " w/Empty Opp Guid");
yield return new TestCaseData(Guid.NewGuid(), configurationGuid, Guid.Empty, true)
.SetName(nameof(DeleteOpportunityLinkTest) + " w/Opp Guid, Empty Db Guid");
yield return new TestCaseData(Guid.NewGuid(), configurationGuid, null, true)
.SetName(nameof(DeleteOpportunityLinkTest) + " w/Opp Guid, Null Db Guid");
yield return new TestCaseData(Guid.NewGuid(), configurationGuid, databaseGuid, false)
.SetName(nameof(DeleteOpportunityLinkTest) + " w/Opp Guid");
}
[TestCaseSource(nameof(DeleteOpportunityLinkIfExistsTestCases))]
public async Task DeleteOpportunityLinkIfExistsTest(Guid opportunityGuid, Guid configurationGuid, bool useEmptyController)
{
// Arrange
Mock.Get(clientNetworkOpportunityLinkService)
.Setup(s => s.ContainsOpportunityLinkAsync(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<Guid>(),
It.IsAny<CancellationToken>()))
.Returns<Guid, Guid, Guid, CancellationToken>(
(dbGuid, configurationGuid, oppGuid, token) =>
oppGuid == Guid.Empty || dbGuid == Guid.Empty
? Task.FromResult(false)
: Task.FromResult(true));
// Act
ActionResult<bool> result;
if (useEmptyController)
{
result = await EmptyDbController.DeleteOpportunityLinkIfExists(opportunityGuid, configurationGuid, CancellationToken.None);
}
else
{
result = await Controller.DeleteOpportunityLinkIfExists(opportunityGuid, configurationGuid, CancellationToken.None);
}
// Assert
result.Should().NotBeNull();
if (useEmptyController)
{
result.Result.Should().BeOfType<BadRequestResult>();
}
}
public static IEnumerable<TestCaseData> DeleteOpportunityLinkIfExistsTestCases()
{
var configurationGuid = JazzEntityFrameworkStub.JazzEntityFrameworkFactory.CurrentConfigurationGuid;
var databaseGuid = JazzEntityFrameworkStub.JazzEntityFrameworkFactory.DatabaseGuid;
yield return new TestCaseData(Guid.Empty, configurationGuid, true)
.SetName(nameof(DeleteOpportunityLinkIfExistsTest) + " w/Empty Opp Guid, Empty Db Guid");
yield return new TestCaseData(Guid.Empty, configurationGuid, true)
.SetName(nameof(DeleteOpportunityLinkIfExistsTest) + " w/Empty Opp Guid, Null Db Guid");
yield return new TestCaseData(Guid.Empty, configurationGuid, false)
.SetName(nameof(DeleteOpportunityLinkIfExistsTest) + " w/Empty Opp Guid");
yield return new TestCaseData(Guid.NewGuid(), configurationGuid, true)
.SetName(nameof(DeleteOpportunityLinkIfExistsTest) + " w/Opp Guid, Empty Db Guid");
yield return new TestCaseData(Guid.NewGuid(), configurationGuid, true)
.SetName(nameof(DeleteOpportunityLinkIfExistsTest) + " w/Opp Guid, Null Db Guid");
yield return new TestCaseData(Guid.NewGuid(), configurationGuid, false)
.SetName(nameof(DeleteOpportunityLinkIfExistsTest) + " w/Opp Guid");
}
[Test()]
public async Task DeleteInitiativeLinkTest()
{
Assert.Inconclusive();
}
[Test()]
public async Task DeleteInitiativeLinkTest1()
{
Assert.Inconclusive();
}
}
}
@@ -0,0 +1,98 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.ClientWarehouses;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class ClientWarehousesControllerTests
{
private readonly ClientWarehousesController _clientWarehouseController;
private readonly Mock<IClientWarehouseService> _mockClientWarehouseService;
public ClientWarehousesControllerTests()
{
_mockClientWarehouseService = new Mock<IClientWarehouseService>();
_clientWarehouseController = new ClientWarehousesController(_mockClientWarehouseService.Object);
}
[Test]
public async Task GetAll_ReturnsAllClientWarehouses()
{
// Arrange
var warehouses = new List<ClientWarehouse>
{
new ClientWarehouse { DatabaseGuid = Guid.NewGuid() },
new ClientWarehouse { DatabaseGuid = Guid.NewGuid() }
};
_mockClientWarehouseService
.Setup(x => x.GetAll(It.IsAny<CancellationToken>()))
.ReturnsAsync(warehouses);
// Act
var result = await _clientWarehouseController.GetClientWarehouses(CancellationToken.None);
// Assert
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
okResult.Value.Should().BeEquivalentTo(warehouses);
}
[Test]
public async Task GetAll_ReturnsNoContent_IfNoneExists()
{
// Arrange
var warehouses = new List<ClientWarehouse>();
_mockClientWarehouseService
.Setup(x => x.GetAll(It.IsAny<CancellationToken>()))
.ReturnsAsync(warehouses);
// Act
var result = await _clientWarehouseController.GetClientWarehouses(CancellationToken.None);
// Assert
var okResult = result.Result.Should().BeOfType<NoContentResult>();
}
[Test]
public async Task Add_ShouldReturnNewId()
{
// Arrange
var clientWarehouse = new ClientWarehouse();
_mockClientWarehouseService
.Setup(s => s.Add(clientWarehouse, It.IsAny<CancellationToken>()))
.ReturnsAsync(123);
// Act
var result = await _clientWarehouseController.AddClientWarehouse(clientWarehouse, CancellationToken.None);
// Assert
var okResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
okResult.Value.Should().Be(123);
}
[Test]
public async Task Delete_ShouldReturn_NoContent()
{
// Arrange
var clientId = Guid.NewGuid();
_mockClientWarehouseService
.Setup(s => s.Delete(clientId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var result = await _clientWarehouseController.DeleteClientWarehouse(clientId, CancellationToken.None);
// Assert
result.Should().BeOfType<NoContentResult>();
}
}
}
@@ -0,0 +1,87 @@
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Api.Test.Unit.Api.Generics;
using Strata.ContinuousImprovement.Biz.Generics;
using Strata.ContinuousImprovement.Biz.OpportunityComments;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.CoreLib.Claims.Extensions;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static Strata.ContinuousImprovement.Api.Controllers.CommentsController;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class CommentsControllerTests
{
[TestCase(true, TestName = "Get Comments Global")]
[TestCase(false, TestName = "Get Comments Not Global")]
public async Task TestGetComments(bool isGlobal)
{
var jeff = new JazzEntityFrameworkFactory();
var dbContext = await jeff.CreateCentralDbContextAsync(nameof(CommentsControllerTests), CancellationToken.None);
var comments = await dbContext.Comments.ToListAsync(CancellationToken.None);
var clientClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
var service = Mock.Of<ICommentService>();
Mock.Get(service)
.Setup(s => s.GetCommentsAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<long>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(comments.Where(c => c.OpportunityId == 1).AsEnumerable()));
var controller = new CommentsController(service, clientClaimsPrincipalAccessor);
var options = new CommentOptions
{
IsGlobal = isGlobal,
OpportunityId = 1L,
OpportunityType = $"{(int)OpportunityTypes.ChargeCode}"
};
var result = await controller.GetComments(options, CancellationToken.None);
result.Should().NotBeEmpty();
}
[TestCase(true, true, TestName = "Add Comment Global")]
[TestCase(false, true, TestName = "Add Comment Not Global")]
[TestCase(true, false, TestName = "Add No Comment Global")]
[TestCase(false, false, TestName = "Add No Comment Not Global")]
public async Task TestAddComments(bool isGlobal, bool hasComment)
{
var jeff = new JazzEntityFrameworkFactory();
var dbContext = await jeff.CreateCentralDbContextAsync(nameof(CommentsControllerTests), CancellationToken.None);
var comments = await dbContext.Comments.ToListAsync(CancellationToken.None);
var newComment = new NewComment
{
IsGlobal = isGlobal,
OpportunityId = 1L,
OpportunityType = $"{(int)OpportunityTypes.ChargeCode}",
CommentDate = DateTimeOffset.Now,
Comment = hasComment ? "This is a new comment" : ""
};
var expected = newComment.Map();
var clientClaimsPrincipalAccessor = TestUtilities.GetClaimsPrincipalAccessor();
var user = clientClaimsPrincipalAccessor.GetCurrentClaimsPrincipal();
var name = user.GetUserFirstName();
var service = Mock.Of<ICommentService>();
Mock.Get(service)
.Setup(s => s.AddCommentAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<long>(),
It.IsAny<string>(), It.IsAny<DateTimeOffset?>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(expected));
var controller = new CommentsController(service, clientClaimsPrincipalAccessor);
var result = await controller.Create(newComment, CancellationToken.None);
if (!hasComment)
{
result.Should().Be(default);
}
else
{
result.Should().BeEquivalentTo(expected);
}
}
}
}
@@ -0,0 +1,128 @@
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.Configuration;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.Schema.Client;
using Strata.Schema.Client.Dtos.Info;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture(), Category("Unit"), Category("Controller")]
public class ConfigurationsControllerTests
{
public JazzDbContext DbContext { get; set; }
public IEnumerable<Biz.Configuration.Configuration> Configurations { get; set; }
public ISchemaServiceClient SchemaService { get; set; }
public IConfigurationService ConfigurationService { get; set; }
[OneTimeSetUp]
public async Task ConfigurationsControllerTestSetup()
{
var jeff = new JazzEntityFrameworkFactory();
DbContext = await jeff.CreateDbContextAsync(nameof(ConfigurationsControllerTests), CancellationToken.None);
Configurations = await DbContext.Configurations.ToListAsync();
var configuration = Configurations.First();
var entities = new HashSet<int>(configuration.ConfiguredEntityIds);
var expected = entities.Select(e => new Member
{
Id = e,
Name = $"Entity {e}"
});
var dimensionInfo = new ScoreDimensionInfoDto
{
FriendlyName = "Entity",
DefaultHierarchyGuid = Guid.NewGuid(),
DimensionGuid = Guid.NewGuid(),
ParentDimensionGuid = Guid.NewGuid()
};
ConfigurationService = Mock.Of<IConfigurationService>();
Mock.Get(ConfigurationService)
.Setup(s => s.GetConfigurationsAsync(It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(Configurations.AsEnumerable()));
Mock.Get(ConfigurationService)
.Setup(s => s.GetConfigurationAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(configuration));
SchemaService = Mock.Of<ISchemaServiceClient>();
Mock.Get(SchemaService)
.Setup(s => s.GetDimensionMembersByGlobalIdAsync<Member>(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(expected));
Mock.Get(SchemaService)
.Setup(s => s.GetDimensionByGlobalIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(dimensionInfo));
Mock.Get(SchemaService)
.Setup(s => s.GetDimensionByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(dimensionInfo));
}
[Test()]
public void ConfigurationsControllerTest()
{
var controller = new ConfigurationsController(ConfigurationService, SchemaService);
controller.Should().NotBeNull();
}
[Test()]
public async Task GetAllConfigurationsTest()
{
var controller = new ConfigurationsController(ConfigurationService, SchemaService);
controller.Should().NotBeNull();
var result = await controller.GetAllConfigurations(CancellationToken.None);
result.Should().BeEquivalentTo(Configurations);
}
[Test()]
public async Task GetConfigurationEntitiesTest()
{
var configuration = Configurations.First();
var entities = new HashSet<int>(configuration.ConfiguredEntityIds);
var expected = entities.Select(e => new Member
{
Id = e,
Name = $"Entity {e}"
});
var controller = new ConfigurationsController(ConfigurationService, SchemaService);
var result = await controller.GetConfigurationEntities(configuration.ConfigurationGuid, CancellationToken.None);
result.Should().BeEquivalentTo(expected);
}
[Test()]
public async Task GetConfigurationEntityInfoTest()
{
var configuration = Configurations.First();
var entities = new HashSet<int>(configuration.ConfiguredEntityIds);
var controller = new ConfigurationsController(ConfigurationService, SchemaService);
var result = await controller.GetConfigurationEntityInfo(configuration.ConfigurationGuid, CancellationToken.None);
result.Should().NotBeNull()
.And.BeOfType<EntityInfo>();
result.EntityTitle.Should().NotBeNullOrEmpty();
result.EntityTitle.Should().Be("Entities");
result.EntityText.Should().Be("Entity");
result.DepartmentRollupGlobalId.Should().Be("Entity");
result.HierarchicalGuid.Should().NotBeEmpty();
}
[Test()]
public async Task GetConfigurationServiceLineInfoTest()
{
var configuration = Configurations.First();
var controller = new ConfigurationsController(ConfigurationService, SchemaService);
var result = await controller.GetConfigurationServiceLineInfo(configuration.ConfigurationGuid, CancellationToken.None);
result.Should().NotBeNull()
.And.BeOfType<ServiceLineInfo>();
result.ServiceLineName.Should().NotBeNullOrEmpty();
result.ServiceLineName.Should().Be("Entity");
result.ServiceLineTitle.Should().Be("Entities");
}
}
}
@@ -0,0 +1,66 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.Databases;
using Strata.ContinuousImprovement.Biz.Replication;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class DatabaseControllerTests
{
private readonly DatabasesController _databaseController;
private readonly Mock<IDatabaseService> _mockDatabaseService;
public DatabaseControllerTests()
{
_mockDatabaseService = new Mock<IDatabaseService>();
_databaseController = new DatabasesController(_mockDatabaseService.Object);
}
[Test]
public async Task Get_ReturnsAllDatabases()
{
// Arrange
var databases = new List<Database>
{
new Database { DatabaseGuid = Guid.NewGuid() },
new Database { DatabaseGuid = Guid.NewGuid() }
};
_mockDatabaseService
.Setup(x => x.GetDatabasesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(databases);
// Act
var result = await _databaseController.Get(CancellationToken.None);
// Assert
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
okResult.Value.Should().BeEquivalentTo(databases);
}
[Test]
public async Task Get_ReturnsNoContent_IfNoneExists()
{
// Arrange
var databases = new List<Database>();
_mockDatabaseService
.Setup(x => x.GetDatabasesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(databases);
// Act
var result = await _databaseController.Get(CancellationToken.None);
// Assert
var okResult = result.Result.Should().BeOfType<NoContentResult>();
}
}
}
@@ -0,0 +1,225 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.Configuration;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.Exploration;
using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies;
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.FeatureFlags.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class ExplorationControllerTests
{
public JazzEntityFrameworkFactory JazzEntityFrameworkFactory { get; set; }
public JazzDbContext JazzDbContext { get; set; }
public ExplorationController ExplorationController { get; set; }
public IConfigurationService ConfigurationService { get; set; }
public IExplorationService ExplorationService { get; set; }
public IFeatureFlagServiceClient FeatureFlagServiceClient { get; set; }
[OneTimeSetUp]
public async Task ClientNetworkOpportunityLinkControllerTestsSetup()
{
// Arrange
JazzEntityFrameworkFactory = new JazzEntityFrameworkFactory();
JazzDbContext = await JazzEntityFrameworkFactory.CreateDbContextAsync(nameof(ConfigurationsControllerTests), CancellationToken.None);
ConfigurationService = Mock.Of<IConfigurationService>();
ExplorationService = Mock.Of<IExplorationService>();
FeatureFlagServiceClient = Mock.Of<IFeatureFlagServiceClient>();
// Act
}
[SetUp]
public async Task Setup()
{
// Arrange
ExplorationController = new ExplorationController(ExplorationService, ConfigurationService, FeatureFlagServiceClient, Mock.Of<ILogger<ExplorationController>>());
// Act
}
[Test]
public async Task ExplorationControllerTest()
{
// Assert
ExplorationController.Should().NotBeNull();
}
[TestCaseSource(nameof(GetCaseTypeFamilySummariesTestCases))]
public async Task GetCaseTypeFamilySummariesTest(Guid configurationGuid)
{
// Arrange
var configuration = JazzDbContext.Configurations.First();
var explorationInfo = new ExplorationInfo
{
ConfigurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid,
ExclusionCriteria = new Biz.Exploration.Filters.ExclusionCriteria(),
Filters = new Biz.Exploration.Filters.Filter
{
DischargeDateStart = new System.DateTime(2024, 1, 1),
DischargeDateEnd = new System.DateTime(2024, 12, 31),
FilterChipItems = new List<FilterChipItem>()
}
};
var result = new List<CaseTypeFamilySummaryData>();
if (configuration.ConfigurationGuid == configurationGuid)
{
Mock.Get(ConfigurationService)
.Setup(s => s.GetById(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>(
(configurationGuid, token) => Task.FromResult(JazzDbContext.Configurations.First()));
Mock.Get(ConfigurationService)
.Setup(s => s.Contains(It.IsAny<Biz.Configuration.Configuration>(), It.IsAny<CancellationToken>()))
.Returns<Biz.Configuration.Configuration, CancellationToken>(
(configuration, token) => Task.FromResult(true));
Mock.Get(ConfigurationService)
.Setup(s => s.IsConfigurationValid(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>(
(configurationGuid, token) => Task.FromResult(true));
}
else
{
Mock.Get(ConfigurationService)
.Setup(s => s.GetById(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>(
(configurationGuid, token) => Task.FromResult(default(Strata.ContinuousImprovement.Biz.Configuration.Configuration)));
Mock.Get(ConfigurationService)
.Setup(s => s.Contains(It.IsAny<Biz.Configuration.Configuration>(), It.IsAny<CancellationToken>()))
.Returns<Biz.Configuration.Configuration, CancellationToken>(
(configuration, token) => Task.FromResult(false));
Mock.Get(ConfigurationService)
.Setup(s => s.IsConfigurationValid(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>(
(configurationGuid, token) => Task.FromResult(false));
}
Mock.Get(ExplorationService)
.Setup(s => s.GetCaseTypeFamilySummariesAsync(It.IsAny<ExplorationInfo>(), It.IsAny<CancellationToken>()))
.Returns<ExplorationInfo, CancellationToken>(
(explorationInfo, token) => Task.FromResult(result.AsEnumerable()));
// Act
var controllerResponse = await ExplorationController.GetCaseTypeFamilySummaries(explorationInfo, CancellationToken.None);
// Assert
controllerResponse.Should().NotBeNull();
if (controllerResponse.Result is OkObjectResult okResult)
{
okResult.Should().NotBeNull();
var value = okResult.Value as IEnumerable<CaseTypeFamilySummaryData>;
value.Should().NotBeNull();
}
else
{
controllerResponse.Result.Should().BeOfType<BadRequestResult>();
}
}
[TestCaseSource(nameof(GetCaseTypeFamilyDetailsTestCases))]
public async Task GetCaseTypeFamilyDetailsTest(Guid configurationGuid)
{
// Arrange
var configuration = JazzDbContext.Configurations.First();
var explorationInfo = new ExplorationDetailInfo
{
ConfigurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid,
CaseTypeFamilyId = 0,
ExclusionCriteria = new Biz.Exploration.Filters.ExclusionCriteria(),
Filters = new Biz.Exploration.Filters.Filter
{
DischargeDateStart = new System.DateTime(2024, 1, 1),
DischargeDateEnd = new System.DateTime(2024, 12, 31),
FilterChipItems = new List<FilterChipItem>()
}
};
var result = new CaseTypeFamilyDetailInfo
{
Metrics = new CaseTypeFamilyMetric(),
CaseTypeFamilyDetails = Enumerable.Empty<CaseTypeFamilyDetailData>()
};
if (configuration.ConfigurationGuid == configurationGuid)
{
Mock.Get(ConfigurationService)
.Setup(s => s.GetById(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>(
(configurationGuid, token) => Task.FromResult(JazzDbContext.Configurations.First()));
Mock.Get(ConfigurationService)
.Setup(s => s.Contains(It.IsAny<Biz.Configuration.Configuration>(), It.IsAny<CancellationToken>()))
.Returns<Biz.Configuration.Configuration, CancellationToken>(
(configuration, token) => Task.FromResult(true));
Mock.Get(ConfigurationService)
.Setup(s => s.IsConfigurationValid(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>(
(configurationGuid, token) => Task.FromResult(true));
}
else
{
Mock.Get(ConfigurationService)
.Setup(s => s.GetById(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>(
(configurationGuid, token) => Task.FromResult(default(Strata.ContinuousImprovement.Biz.Configuration.Configuration)));
Mock.Get(ConfigurationService)
.Setup(s => s.Contains(It.IsAny<Biz.Configuration.Configuration>(), It.IsAny<CancellationToken>()))
.Returns<Biz.Configuration.Configuration, CancellationToken>(
(configuration, token) => Task.FromResult(false));
Mock.Get(ConfigurationService)
.Setup(s => s.IsConfigurationValid(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>(
(configurationGuid, token) => Task.FromResult(false));
}
Mock.Get(ExplorationService)
.Setup(s => s.GetCaseTypeFamilyDetailsAsync(It.IsAny<ExplorationDetailInfo>(), It.IsAny<CancellationToken>()))
.Returns<ExplorationDetailInfo, CancellationToken>(
(exploration, token) => Task.FromResult(result));
// Act
var controllerResponse = await ExplorationController.GetCaseTypeFamilyDetails(explorationInfo, CancellationToken.None);
// Assert
controllerResponse.Should().NotBeNull();
if (controllerResponse.Result is OkObjectResult okResult)
{
okResult.Should().NotBeNull();
okResult.Value.Should().BeOfType<CaseTypeFamilyDetailInfo>();
configuration.ConfigurationGuid.Should().Be(configurationGuid);
}
else if (controllerResponse.Result is BadRequestResult badRequestResult)
{
badRequestResult.Should().NotBeNull();
}
}
public static IEnumerable<TestCaseData> GetCaseTypeFamilySummariesTestCases()
{
var configurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid;
yield return new TestCaseData(configurationGuid)
.SetName("summaries valid configuration");
yield return new TestCaseData(Guid.NewGuid())
.SetName("summaries invalid configuration");
}
public static IEnumerable<TestCaseData> GetCaseTypeFamilyDetailsTestCases()
{
var configurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid;
yield return new TestCaseData(configurationGuid)
.SetName("details valid configuration");
yield return new TestCaseData(Guid.NewGuid())
.SetName("details invalid configuration");
}
}
}
@@ -0,0 +1,92 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.Dtos;
using Strata.ContinuousImprovement.Biz.Exploration;
using Strata.ContinuousImprovement.Biz.Population;
using Strata.ContinuousImprovement.Biz.UserSetting;
using Strata.DecisionSupport.Models.Encounters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class ExplorationPopulationsControllerTests
{
public ExplorationPopulationsController Controller { get; set; }
public IUserSettingService UserSettingService { get; set; }
public IExplorationPopulationService PopulationService { get; set; }
public IExplorationService ExplorationService { get; set; }
[OneTimeSetUp]
public async Task ExplorationPopulationsControllerTests_OneTimeSetUp()
{
UserSettingService = Mock.Of<IUserSettingService>();
PopulationService = Mock.Of<IExplorationPopulationService>();
ExplorationService = Mock.Of<IExplorationService>();
}
[SetUp]
public void ExplorationPopulationsControllerTests_Setup()
{
Controller = new ExplorationPopulationsController(UserSettingService, PopulationService, ExplorationService, Mock.Of<ILogger<ExplorationPopulationsController>>());
}
[TestCaseSource(nameof(GetEncountersTestCases))]
public async Task GetEncountersTest(ExplorationPopulationDto population, IEnumerable<EncounterPhysicianInfo> encounterPhysicians)
{
// Arrange
long populationId = 0;
Guid configurationGuid = new Guid();
Mock.Get(PopulationService)
.Setup(s => s.GetPopulationDataAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.Returns<long, CancellationToken>((populationId, token) => Task.FromResult(population));
Mock.Get(ExplorationService)
.Setup(s => s.GetEncountersAsync(It.IsAny<ExplorationDetailInfo>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<ExplorationDetailInfo, Guid, CancellationToken>((exploration, configurationGuid, token) => Task.FromResult(encounterPhysicians));
// Act
var response = await Controller.GetEncounters(populationId, configurationGuid, CancellationToken.None);
// Assert
response.Should().NotBeNull();
if (population == null || encounterPhysicians == null)
{
response.Result.Should().BeOfType<BadRequestResult>();
}
else
{
response.Result.Should().BeOfType<OkObjectResult>();
var okResult = (OkObjectResult)response.Result;
okResult.Value.Should().BeAssignableTo<IEnumerable<IJazzExplorationEncounterInfo>>();
okResult.Value.Should().NotBeNull();
}
}
public static IEnumerable<TestCaseData> GetEncountersTestCases()
{
var population = new ExplorationPopulationDto()
{
FiltersJSON = string.Empty
};
var encounterPhysicians = Enumerable.Empty<EncounterPhysicianInfo>();
yield return new TestCaseData(population, encounterPhysicians)
.SetName("Population and Encounters are not null");
yield return new TestCaseData(population, null)
.SetName("Encounters is null");
yield return new TestCaseData(null, encounterPhysicians)
.SetName("Population is null");
yield return new TestCaseData(null, null)
.SetName("Population and Encounters are null");
}
}
}
@@ -0,0 +1,180 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using Strata.ApiLib.Standard.Models;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives;
using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities;
using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities.Filters;
using Strata.ContinuousImprovement.Biz.Initiatives;
using Strata.ContinuousImprovement.Biz.Pagination;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class FreeFormOpportunitiesControllerTests
{
public JazzEntityFrameworkFactory jazzEntityFrameworkFactory { get; set; }
public JazzDbContext DbContext { get; set; }
public IFFOpportunityService FFOpportunityService { get; set; }
public IFFInitiativeService FFInitiativeService { get; set; }
public IInitiativeService InitiativeService { get; set; }
public FreeFormOpportunitiesController Controller { get; set; }
[OneTimeSetUp]
public async Task FreeFormOpportunitiesControllerTestSetup()
{
jazzEntityFrameworkFactory = new JazzEntityFrameworkFactory();
DbContext = await jazzEntityFrameworkFactory.CreateDbContextAsync(nameof(ConfigurationsControllerTests), CancellationToken.None);
FFOpportunityService = Mock.Of<IFFOpportunityService>();
InitiativeService = Mock.Of<IInitiativeService>();
}
[SetUp]
public async Task Setup()
{
// Arrange
Controller = new FreeFormOpportunitiesController(FFOpportunityService, InitiativeService);
}
[Test()]
public async Task FreeFormOpportunitiesControllerTest()
{
// Act
// Assert
Controller.Should().NotBeNull();
}
[Test()]
public async Task GetOpportunitiesTest()
{
// Arrange
var opportunities = DbContext.FFOpportunities.Take(25).ToList();
var pagingOptions = new PagingOptions
{
First = 1,
Rows = 25,
SortField = nameof(FFOpportunity.IdentifiedSavings),
SortOrder = SortOrder.Asc
};
var loadOptions = new FFOpportunityLoadOptions
{
Filters = new FFFilters { ConfigurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid },
PagingOptions = pagingOptions,
};
var response = new PagedApiResponse<FFOpportunity>
{
PagingInfo = new PagingInfo
{
PageNumber = 1,
TotalNumberOfResults = opportunities.Count,
TotalPages = opportunities.Count / pagingOptions.Rows,
ResultsPerPage = pagingOptions.Rows
},
Data = opportunities
};
Mock.Get(FFOpportunityService)
.Setup(x => x.GetPagedAsync(It.IsAny<PagingOptions>(), It.IsAny<FFFilters>(), It.IsAny<CancellationToken>()))
.Returns<PagingOptions, FFFilters, CancellationToken>(
(pagingOptions, filters, token) =>
Task.FromResult(response)
);
// Act
var controllerResponse = await Controller.GetOpportunities(loadOptions, CancellationToken.None);
// Assert
controllerResponse.Should().NotBeNull();
}
[Test()]
public async Task GetByIdTest()
{
// Arrange
var opportunities = DbContext.FreeFormOpportunities.ToList();
var firstOpportunity = opportunities.First();
if (firstOpportunity.Configuration == null)
{
var configuration = DbContext.Configurations.First();
if (configuration.FiscalMonthResolver == null)
{
var fmr = jazzEntityFrameworkFactory.FiscalMonthResolver;
configuration.FiscalMonthResolver = fmr;
}
firstOpportunity.Configuration = configuration;
}
var dfltOpportunity = default(FFOpportunityDataModel);
var fmResolver = jazzEntityFrameworkFactory.FiscalMonthResolver;
Mock.Get(FFOpportunityService)
.Setup(x => x.GetDataModelById(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns<Guid, CancellationToken>(
(opportunityGuid, token) =>
opportunityGuid != firstOpportunity.OpportunityGuid
? Task.FromResult(dfltOpportunity)
: Task.FromResult(firstOpportunity)
);
var detailModel = firstOpportunity.MapToDetailModel(fmResolver);
// Act
var controllerResponse = await Controller.GetById(firstOpportunity.OpportunityGuid, CancellationToken.None);
// Assert
controllerResponse.Should().NotBeNull();
controllerResponse.Result.Should().NotBeNull();
controllerResponse.Result.Should().BeOfType<OkObjectResult>();
var result = (OkObjectResult)controllerResponse.Result;
result.Should().NotBeNull();
result.Should().BeOfType<OkObjectResult>();
result.Value.Should().NotBeNull();
result.Value.Should().BeOfType<FFOpportunityDetailModel>();
var detail = (FFOpportunityDetailModel)result.Value;
detail.Should().BeEquivalentTo(detailModel);
}
[Test()]
public async Task CreateTest()
{
Assert.Inconclusive();
}
[Test()]
public async Task DeleteTest()
{
Assert.Inconclusive();
}
[Test()]
public async Task GetInitiativeTest()
{
Assert.Inconclusive();
}
[Test()]
public async Task SetGoalsAsyncTest()
{
Assert.Inconclusive();
}
[Test()]
public async Task ExportTest()
{
Assert.Inconclusive();
}
[Test()]
public async Task GetFilterOptionsAsyncTest()
{
Assert.Inconclusive();
}
}
}
@@ -0,0 +1,83 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers.JazzQvi;
using Strata.ContinuousImprovement.Biz.QualityVariation.QVI;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class NonQviSavingsControllerTests
{
private Mock<INonQviSavingsService> _serviceMock;
private NonQviSavingsController _controller;
[SetUp]
public void SetUp()
{
_serviceMock = new Mock<INonQviSavingsService>();
_controller = new NonQviSavingsController(_serviceMock.Object);
}
[Test]
public async Task GetCaseTypeFamilies_WhenConfigurationGuidIsEmpty_ReturnsBadRequest()
{
var result = await _controller.GetCaseTypeFamilies(Guid.Empty, false, CancellationToken.None);
result.Result.Should().BeOfType<BadRequestObjectResult>();
_serviceMock.Verify(s => s.GetCaseTypeFamilyInfoAsync(It.IsAny<Guid>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task GetCaseTypeFamilies_WhenConfigurationGuidIsValid_ReturnsOk()
{
var configurationGuid = Guid.NewGuid();
var expected = new[] { new NonQviCaseTypeFamilyInfo { CaseTypeFamilyId = 1, CaseCount = 5 } };
_serviceMock
.Setup(s => s.GetCaseTypeFamilyInfoAsync(configurationGuid, true, It.IsAny<CancellationToken>()))
.ReturnsAsync(expected);
var result = await _controller.GetCaseTypeFamilies(configurationGuid, true, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = result.Result as OkObjectResult;
var value = okResult?.Value as System.Collections.Generic.IEnumerable<NonQviCaseTypeFamilyInfo>;
value.Should().NotBeNull();
value!.Single().CaseTypeFamilyId.Should().Be(1);
_serviceMock.Verify(s => s.GetCaseTypeFamilyInfoAsync(configurationGuid, true, It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public async Task GetDrgTypes_WhenConfigurationGuidIsEmpty_ReturnsBadRequest()
{
var result = await _controller.GetDrgTypes(Guid.Empty, false, CancellationToken.None);
result.Result.Should().BeOfType<BadRequestObjectResult>();
_serviceMock.Verify(s => s.GetDrgTypeInfoAsync(It.IsAny<Guid>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task GetDrgTypes_WhenConfigurationGuidIsValid_ReturnsOk()
{
var configurationGuid = Guid.NewGuid();
var expected = new[] { new NonQviDrgTypeInfo { DrgType = "MS" } };
_serviceMock
.Setup(s => s.GetDrgTypeInfoAsync(configurationGuid, true, It.IsAny<CancellationToken>()))
.ReturnsAsync(expected);
var result = await _controller.GetDrgTypes(configurationGuid, true, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = result.Result as OkObjectResult;
var value = okResult?.Value as System.Collections.Generic.IEnumerable<NonQviDrgTypeInfo>;
value.Should().NotBeNull();
value!.Single().DrgType.Should().Be("MS");
_serviceMock.Verify(s => s.GetDrgTypeInfoAsync(configurationGuid, true, It.IsAny<CancellationToken>()), Times.Once);
}
}
}
@@ -0,0 +1,57 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers.JazzQvi;
using Strata.ContinuousImprovement.Biz.QualityVariation.QVI;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class QviEncountersControllerTests
{
private Mock<IQviEncounterService> _serviceMock;
private QviEncountersController _controller;
[SetUp]
public void SetUp()
{
_serviceMock = new Mock<IQviEncounterService>();
_controller = new QviEncountersController(_serviceMock.Object);
}
[Test]
public async Task Get_WhenConfigurationGuidIsEmpty_ReturnsBadRequest()
{
var result = await _controller.Get(Guid.Empty, DateTime.UtcNow, DateTime.UtcNow, CancellationToken.None);
result.Result.Should().BeOfType<BadRequestObjectResult>();
_serviceMock.Verify(s => s.GetEncounterInfoAsync(It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task Get_WhenConfigurationGuidIsValid_ReturnsOk()
{
var configurationGuid = Guid.NewGuid();
var startDate = new DateTime(2024, 1, 1);
var endDate = new DateTime(2024, 12, 31);
var expected = new[] { new EncounterInfo { EncounterId = 11, EncounterRecordNumber = "enc-11" } };
_serviceMock
.Setup(s => s.GetEncounterInfoAsync(configurationGuid, startDate, endDate, It.IsAny<CancellationToken>()))
.ReturnsAsync(expected);
var result = await _controller.Get(configurationGuid, startDate, endDate, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = result.Result as OkObjectResult;
var value = okResult?.Value as System.Collections.Generic.IEnumerable<EncounterInfo>;
value.Should().NotBeNull();
value!.Single().EncounterId.Should().Be(11);
_serviceMock.Verify(s => s.GetEncounterInfoAsync(configurationGuid, startDate, endDate, It.IsAny<CancellationToken>()), Times.Once);
}
}
}
@@ -0,0 +1,111 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers.JazzQvi;
using Strata.ContinuousImprovement.Biz.QualityVariation.QVI;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[TestFixture, Category("Unit"), Category("Controller")]
public class QviLineItemSavingsControllerTests
{
private Mock<IQviLineItemSavingsService> _serviceMock;
private QviLineItemSavingsController _controller;
[SetUp]
public void SetUp()
{
_serviceMock = new Mock<IQviLineItemSavingsService>();
_controller = new QviLineItemSavingsController(_serviceMock.Object);
}
[Test]
public async Task GetCaseTypeFamilies_WhenConfigurationGuidIsEmpty_ReturnsBadRequest()
{
var result = await _controller.GetCaseTypeFamilies(Guid.Empty, false, CancellationToken.None);
result.Result.Should().BeOfType<BadRequestObjectResult>();
_serviceMock.Verify(s => s.GetCaseTypeFamilyInfoAsync(It.IsAny<Guid>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task GetCaseTypeFamilies_WhenConfigurationGuidIsValid_ReturnsOk()
{
var configurationGuid = Guid.NewGuid();
var expected = new[] { new QviLineItemCaseTypeFamilyInfo { QualityVariationEventId = 1, CaseTypeFamilyId = 2 } };
_serviceMock
.Setup(s => s.GetCaseTypeFamilyInfoAsync(configurationGuid, true, It.IsAny<CancellationToken>()))
.ReturnsAsync(expected);
var result = await _controller.GetCaseTypeFamilies(configurationGuid, true, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = result.Result as OkObjectResult;
var value = okResult?.Value as System.Collections.Generic.IEnumerable<QviLineItemCaseTypeFamilyInfo>;
value.Should().NotBeNull();
value!.Single().CaseTypeFamilyId.Should().Be(2);
_serviceMock.Verify(s => s.GetCaseTypeFamilyInfoAsync(configurationGuid, true, It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public async Task GetDrgTypes_WhenConfigurationGuidIsEmpty_ReturnsBadRequest()
{
var result = await _controller.GetDrgTypes(Guid.Empty, false, CancellationToken.None);
result.Result.Should().BeOfType<BadRequestObjectResult>();
_serviceMock.Verify(s => s.GetDrgTypeInfoAsync(It.IsAny<Guid>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task GetDrgTypes_WhenConfigurationGuidIsValid_ReturnsOk()
{
var configurationGuid = Guid.NewGuid();
var expected = new[] { new QviLineItemDrgTypeInfo { DrgType = "APR" } };
_serviceMock
.Setup(s => s.GetDrgTypeInfoAsync(configurationGuid, true, It.IsAny<CancellationToken>()))
.ReturnsAsync(expected);
var result = await _controller.GetDrgTypes(configurationGuid, true, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = result.Result as OkObjectResult;
var value = okResult?.Value as System.Collections.Generic.IEnumerable<QviLineItemDrgTypeInfo>;
value.Should().NotBeNull();
value!.Single().DrgType.Should().Be("APR");
_serviceMock.Verify(s => s.GetDrgTypeInfoAsync(configurationGuid, true, It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public async Task GetEncounterSavings_WhenConfigurationGuidIsEmpty_ReturnsBadRequest()
{
var result = await _controller.GetEncounterSavings(Guid.Empty, cancellationToken: CancellationToken.None);
result.Result.Should().BeOfType<BadRequestObjectResult>();
_serviceMock.Verify(s => s.GetEncounterSavingInfoAsync(It.IsAny<Guid>(), It.IsAny<Guid?>(), It.IsAny<bool?>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task GetEncounterSavings_WhenConfigurationGuidIsValid_ReturnsOk()
{
var configurationGuid = Guid.NewGuid();
var expected = new[] { new QviLineItemEncounterSavingInfo { EncounterRecordNumber = "enc-1" } };
_serviceMock
.Setup(s => s.GetEncounterSavingInfoAsync(configurationGuid, It.IsAny<Guid?>(), It.IsAny<bool?>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(expected);
var result = await _controller.GetEncounterSavings(configurationGuid, cancellationToken: CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
var okResult = result.Result as OkObjectResult;
var value = okResult?.Value as System.Collections.Generic.IEnumerable<QviLineItemEncounterSavingInfo>;
value.Should().NotBeNull();
value!.Single().EncounterRecordNumber.Should().Be("enc-1");
_serviceMock.Verify(s => s.GetEncounterSavingInfoAsync(configurationGuid, null, null, It.IsAny<CancellationToken>()), Times.Once);
}
}
}
@@ -0,0 +1,337 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[ExcludeFromCodeCoverage]
[TestFixture, Category("Unit"), Category("Controller")]
public class StrategicChargeCodeControllerTests
{
private StrategicChargeCodeController _controller;
private Mock<IStrategicOpportunityService> _mockOpportunityService;
private Mock<IStrategicChargeCodeOpportunityDetailService> _mockDetailService;
[SetUp]
public void SetUp()
{
_mockOpportunityService = new Mock<IStrategicOpportunityService>();
_mockDetailService = new Mock<IStrategicChargeCodeOpportunityDetailService>();
_controller = new StrategicChargeCodeController(_mockOpportunityService.Object, _mockDetailService.Object);
}
// --- GetOpportunityChargeCode ---
[Test]
public async Task GetOpportunityChargeCode_ReturnsOk_WhenFound()
{
const long opportunityId = 1;
var data = new StrategicOpportunityChargeCodeEditData();
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityChargeCode(opportunityId, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetOpportunityChargeCode(opportunityId, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetOpportunityChargeCode_ReturnsNotFound_WhenMissing()
{
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityChargeCode(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityChargeCodeEditData)null);
var result = await _controller.GetOpportunityChargeCode(99, CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- CreateOpportunityChargeCode ---
[Test]
public async Task CreateOpportunityChargeCode_ReturnsOk_WithNewId_WhenSuccessful()
{
const long newId = 42;
var dto = new StrategicOpportunityChargeCodeDto();
_mockOpportunityService
.Setup(s => s.CreateOpportunityChargeCodeAsync(dto, It.IsAny<CancellationToken>()))
.ReturnsAsync(newId);
var result = await _controller.CreateOpportunityChargeCode(dto, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(newId);
}
[Test]
public async Task CreateOpportunityChargeCode_ReturnsBadRequest_WhenServiceReturnsZero()
{
_mockOpportunityService
.Setup(s => s.CreateOpportunityChargeCodeAsync(It.IsAny<StrategicOpportunityChargeCodeDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0L);
var result = await _controller.CreateOpportunityChargeCode(new StrategicOpportunityChargeCodeDto(), CancellationToken.None);
result.Result.Should().BeOfType<BadRequestResult>();
}
// --- UpdateOpportunityChargeCode ---
[Test]
public async Task UpdateOpportunityChargeCode_ReturnsOk_WhenSuccessful()
{
const long opportunityId = 5;
_mockOpportunityService
.Setup(s => s.UpdateOpportunityChargeCode(opportunityId, It.IsAny<StrategicOpportunityChargeCodeUpdateDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(opportunityId);
var result = await _controller.UpdateOpportunityChargeCode(opportunityId, new StrategicOpportunityChargeCodeUpdateDto(), CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
}
[Test]
public async Task UpdateOpportunityChargeCode_ReturnsNotFound_WhenServiceReturnsZero()
{
_mockOpportunityService
.Setup(s => s.UpdateOpportunityChargeCode(It.IsAny<long>(), It.IsAny<StrategicOpportunityChargeCodeUpdateDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0L);
var result = await _controller.UpdateOpportunityChargeCode(99, new StrategicOpportunityChargeCodeUpdateDto(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- DeleteOpportunityChargeCode ---
[Test]
public async Task DeleteOpportunityChargeCode_ReturnsNotFound_WhenOpportunityDoesNotExist()
{
_mockOpportunityService
.Setup(s => s.ContainsAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await _controller.DeleteOpportunityChargeCode(99, CancellationToken.None);
result.Should().BeOfType<NotFoundResult>();
_mockOpportunityService.Verify(s => s.DeleteChargeCodeOpportunityAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task DeleteOpportunityChargeCode_ReturnsNoContent_WhenDeleted()
{
const long opportunityId = 7;
_mockOpportunityService.Setup(s => s.ContainsAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
_mockOpportunityService.Setup(s => s.DeleteChargeCodeOpportunityAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
var result = await _controller.DeleteOpportunityChargeCode(opportunityId, CancellationToken.None);
result.Should().BeOfType<NoContentResult>();
}
[Test]
public async Task DeleteOpportunityChargeCode_ReturnsBadRequest_WhenDeleteFails()
{
const long opportunityId = 7;
_mockOpportunityService.Setup(s => s.ContainsAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
_mockOpportunityService.Setup(s => s.DeleteChargeCodeOpportunityAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(false);
var result = await _controller.DeleteOpportunityChargeCode(opportunityId, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
}
// --- GetGoalDetail ---
[Test]
public async Task GetGoalDetail_ReturnsOk_WhenFound()
{
const long opportunityId = 3;
var getData = new StrategicOpportunityGetDetailData();
var data = new StrategicOpportunityDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(opportunityId, false, getData, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetGoalDetail(opportunityId, getData, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetGoalDetail_ReturnsNotFound_WhenMissing()
{
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(It.IsAny<long>(), false, It.IsAny<StrategicOpportunityGetDetailData>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityDetailData)null);
var result = await _controller.GetGoalDetail(99, new StrategicOpportunityGetDetailData(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
[Test]
public async Task GetGoalDetail_PassesIsTrackingFalse_ToService()
{
const long opportunityId = 3;
var getData = new StrategicOpportunityGetDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(opportunityId, false, getData, It.IsAny<CancellationToken>()))
.ReturnsAsync(new StrategicOpportunityDetailData());
await _controller.GetGoalDetail(opportunityId, getData, CancellationToken.None);
_mockDetailService.Verify(s => s.GetStrategicOpportunityDetailData(opportunityId, false, getData, CancellationToken.None), Times.Once);
}
// --- GetTrackingDetail ---
[Test]
public async Task GetTrackingDetail_ReturnsOk_WhenFound()
{
const long opportunityId = 4;
var getData = new StrategicOpportunityGetDetailData();
var data = new StrategicOpportunityDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(opportunityId, true, getData, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetTrackingDetail(opportunityId, getData, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetTrackingDetail_ReturnsNotFound_WhenMissing()
{
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(It.IsAny<long>(), true, It.IsAny<StrategicOpportunityGetDetailData>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityDetailData)null);
var result = await _controller.GetTrackingDetail(99, new StrategicOpportunityGetDetailData(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
[Test]
public async Task GetTrackingDetail_PassesIsTrackingTrue_ToService()
{
const long opportunityId = 4;
var getData = new StrategicOpportunityGetDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(opportunityId, true, getData, It.IsAny<CancellationToken>()))
.ReturnsAsync(new StrategicOpportunityDetailData());
await _controller.GetTrackingDetail(opportunityId, getData, CancellationToken.None);
_mockDetailService.Verify(s => s.GetStrategicOpportunityDetailData(opportunityId, true, getData, CancellationToken.None), Times.Once);
}
// --- UpdateDetails ---
[Test]
public async Task UpdateDetails_ReturnsBadRequest_WhenDtoIsNull()
{
var result = await _controller.UpdateDetails(1, null, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityChargeCodeAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateDetails_ReturnsBadRequest_WhenAdjustmentsIsEmpty()
{
var dto = new StrategicChargeCodeController.UpdateDetailsDto
{
Adjustments = new List<StrategicOpportunityChargeCodeAdjustment>(),
Rows = new List<string> { "system", "entity" }
};
var result = await _controller.UpdateDetails(1, dto, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityChargeCodeAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateDetails_ReturnsBadRequest_WhenRowsIsEmpty()
{
var dto = new StrategicChargeCodeController.UpdateDetailsDto
{
Adjustments = new List<StrategicOpportunityChargeCodeAdjustment> { new() { NodeKey = "101", CostsValue = 5000m } },
Rows = new List<string>()
};
var result = await _controller.UpdateDetails(1, dto, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityChargeCodeAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateDetails_ReturnsOk_AndCallsService_WhenDtoIsValid()
{
const long opportunityId = 42;
var dto = new StrategicChargeCodeController.UpdateDetailsDto
{
Adjustments = new List<StrategicOpportunityChargeCodeAdjustment> { new() { NodeKey = "101", CostsValue = 5000m } },
Rows = new List<string> { "system", "entity", "chargeCode" }
};
var result = await _controller.UpdateDetails(opportunityId, dto, CancellationToken.None);
result.Should().BeOfType<OkResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityDetailsData(
opportunityId, dto.Adjustments, dto.Rows, CancellationToken.None), Times.Once);
}
// --- Route attribute assertions ---
[Test]
public void Controller_HasExpectedRouteTemplate()
{
var routeAttr = typeof(StrategicChargeCodeController).GetCustomAttribute<RouteAttribute>();
routeAttr.Should().NotBeNull();
routeAttr.Template.Should().Be("api/v{api-version:apiVersion}/[controller]");
}
[TestCase(nameof(StrategicChargeCodeController.GetOpportunityChargeCode), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicChargeCodeController.CreateOpportunityChargeCode), typeof(HttpPostAttribute), null)]
[TestCase(nameof(StrategicChargeCodeController.UpdateOpportunityChargeCode), typeof(HttpPutAttribute), "{opportunityId}")]
[TestCase(nameof(StrategicChargeCodeController.DeleteOpportunityChargeCode), typeof(HttpDeleteAttribute), "{opportunityId}")]
[TestCase(nameof(StrategicChargeCodeController.GetGoalDetail), typeof(HttpPostAttribute), "{opportunityId}/goal-detail")]
[TestCase(nameof(StrategicChargeCodeController.ExportInitiative), typeof(HttpPostAttribute), "{opportunityId}/export")]
[TestCase(nameof(StrategicChargeCodeController.ExportOpportunity), typeof(HttpPostAttribute), "{opportunityId}/export-opportunity")]
[TestCase(nameof(StrategicChargeCodeController.GetTrackingDetail), typeof(HttpPostAttribute), "{opportunityId}/tracking-detail")]
[TestCase(nameof(StrategicChargeCodeController.UpdateDetails), typeof(HttpPutAttribute), "{opportunityId}/details")]
public void ActionRoute_HasExpectedHttpMethodAndTemplate(string methodName, Type httpAttrType, string expectedTemplate)
{
var method = typeof(StrategicChargeCodeController).GetMethod(methodName);
method.Should().NotBeNull($"method '{methodName}' should exist on {nameof(StrategicChargeCodeController)}");
var attr = method.GetCustomAttribute(httpAttrType);
attr.Should().NotBeNull($"{methodName} should be decorated with [{httpAttrType.Name}]");
var template = (string)attr.GetType().GetProperty("Template").GetValue(attr);
template.Should().Be(expectedTemplate);
}
}
}
@@ -0,0 +1,335 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[ExcludeFromCodeCoverage]
[TestFixture, Category("Unit"), Category("Controller")]
public class StrategicGeneralLedgerControllerTests
{
private StrategicGeneralLedgerController _controller;
private Mock<IStrategicOpportunityService> _mockOpportunityService;
private Mock<IStrategicGLOpportunityDetailService> _mockDetailService;
[SetUp]
public void SetUp()
{
_mockOpportunityService = new Mock<IStrategicOpportunityService>();
_mockDetailService = new Mock<IStrategicGLOpportunityDetailService>();
_controller = new StrategicGeneralLedgerController(_mockOpportunityService.Object, _mockDetailService.Object);
}
// --- GetOpportunityGL ---
[Test]
public async Task GetOpportunityGL_ReturnsOk_WhenFound()
{
const long opportunityId = 1;
var data = new StrategicOpportunityGLEditData();
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityGL(opportunityId, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetOpportunityGL(opportunityId, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetOpportunityGL_ReturnsNotFound_WhenMissing()
{
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityGL(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityGLEditData)null);
var result = await _controller.GetOpportunityGL(99, CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- CreateOpportunityGeneralLedger ---
[Test]
public async Task CreateOpportunityGeneralLedger_ReturnsOk_WithNewId_WhenSuccessful()
{
const long newId = 42;
var dto = new StrategicOpportunityGLDto();
_mockOpportunityService
.Setup(s => s.CreateOpportunityGeneralLedgerAsync(dto, It.IsAny<CancellationToken>()))
.ReturnsAsync(newId);
var result = await _controller.CreateOpportunityGeneralLedger(dto, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(newId);
}
[Test]
public async Task CreateOpportunityGeneralLedger_ReturnsBadRequest_WhenServiceReturnsZero()
{
_mockOpportunityService
.Setup(s => s.CreateOpportunityGeneralLedgerAsync(It.IsAny<StrategicOpportunityGLDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0L);
var result = await _controller.CreateOpportunityGeneralLedger(new StrategicOpportunityGLDto(), CancellationToken.None);
result.Result.Should().BeOfType<BadRequestResult>();
}
// --- UpdateOpportunityGeneralLedger ---
[Test]
public async Task UpdateOpportunityGeneralLedger_ReturnsOk_WhenSuccessful()
{
const long opportunityId = 5;
_mockOpportunityService
.Setup(s => s.UpdateOpportunityGeneralLedger(opportunityId, It.IsAny<StrategicOpportunityGLUpdateDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(opportunityId);
var result = await _controller.UpdateOpportunityGeneralLedger(opportunityId, new StrategicOpportunityGLUpdateDto(), CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
}
[Test]
public async Task UpdateOpportunityGeneralLedger_ReturnsNotFound_WhenServiceReturnsZero()
{
_mockOpportunityService
.Setup(s => s.UpdateOpportunityGeneralLedger(It.IsAny<long>(), It.IsAny<StrategicOpportunityGLUpdateDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0L);
var result = await _controller.UpdateOpportunityGeneralLedger(99, new StrategicOpportunityGLUpdateDto(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- DeleteOpportunityGeneralLedger ---
[Test]
public async Task DeleteOpportunityGeneralLedger_ReturnsNotFound_WhenOpportunityDoesNotExist()
{
_mockOpportunityService
.Setup(s => s.ContainsGLAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await _controller.DeleteOpportunityGeneralLedger(99, CancellationToken.None);
result.Should().BeOfType<NotFoundResult>();
_mockOpportunityService.Verify(s => s.DeleteGeneralLedgerOpportunityAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task DeleteOpportunityGeneralLedger_ReturnsNoContent_WhenDeleted()
{
const long opportunityId = 7;
_mockOpportunityService.Setup(s => s.ContainsGLAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
_mockOpportunityService.Setup(s => s.DeleteGeneralLedgerOpportunityAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
var result = await _controller.DeleteOpportunityGeneralLedger(opportunityId, CancellationToken.None);
result.Should().BeOfType<NoContentResult>();
}
[Test]
public async Task DeleteOpportunityGeneralLedger_ReturnsBadRequest_WhenDeleteFails()
{
const long opportunityId = 7;
_mockOpportunityService.Setup(s => s.ContainsGLAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
_mockOpportunityService.Setup(s => s.DeleteGeneralLedgerOpportunityAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(false);
var result = await _controller.DeleteOpportunityGeneralLedger(opportunityId, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
}
// --- GetGLDetail ---
[Test]
public async Task GetGLDetail_ReturnsOk_WhenFound()
{
const long opportunityId = 3;
var dimensions = new[] { "system", "entity" };
var data = new StrategicOpportunityGLDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(opportunityId, false, dimensions, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetGLDetail(opportunityId, dimensions, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetGLDetail_ReturnsNotFound_WhenMissing()
{
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(It.IsAny<long>(), false, It.IsAny<string[]>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityGLDetailData)null);
var result = await _controller.GetGLDetail(99, Array.Empty<string>(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
[Test]
public async Task GetGLDetail_PassesIsTrackingFalse_ToService()
{
const long opportunityId = 3;
var dimensions = new[] { "system" };
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(opportunityId, false, dimensions, It.IsAny<CancellationToken>()))
.ReturnsAsync(new StrategicOpportunityGLDetailData());
await _controller.GetGLDetail(opportunityId, dimensions, CancellationToken.None);
_mockDetailService.Verify(s => s.GetStrategicOpportunityGLDetailData(opportunityId, false, dimensions, CancellationToken.None), Times.Once);
}
// --- GetGLTrackingDetail ---
[Test]
public async Task GetGLTrackingDetail_ReturnsOk_WhenFound()
{
const long opportunityId = 4;
var dimensions = new[] { "system", "glAccount" };
var data = new StrategicOpportunityGLDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(opportunityId, true, dimensions, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetGLTrackingDetail(opportunityId, dimensions, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetGLTrackingDetail_ReturnsNotFound_WhenMissing()
{
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(It.IsAny<long>(), true, It.IsAny<string[]>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityGLDetailData)null);
var result = await _controller.GetGLTrackingDetail(99, Array.Empty<string>(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
[Test]
public async Task GetGLTrackingDetail_PassesIsTrackingTrue_ToService()
{
const long opportunityId = 4;
var dimensions = new[] { "system" };
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(opportunityId, true, dimensions, It.IsAny<CancellationToken>()))
.ReturnsAsync(new StrategicOpportunityGLDetailData());
await _controller.GetGLTrackingDetail(opportunityId, dimensions, CancellationToken.None);
_mockDetailService.Verify(s => s.GetStrategicOpportunityGLDetailData(opportunityId, true, dimensions, CancellationToken.None), Times.Once);
}
// --- UpdateGLDetails ---
[Test]
public async Task UpdateGLDetails_ReturnsBadRequest_WhenDtoIsNull()
{
var result = await _controller.UpdateGLDetails(1, null, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityGLDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityGLAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateGLDetails_ReturnsBadRequest_WhenAdjustmentsIsEmpty()
{
var dto = new UpdateGLDetailsDto
{
Adjustments = new List<StrategicOpportunityGLAdjustment>(),
Rows = new List<string> { "system", "entity" }
};
var result = await _controller.UpdateGLDetails(1, dto, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityGLDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityGLAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateGLDetails_ReturnsBadRequest_WhenRowsIsEmpty()
{
var dto = new UpdateGLDetailsDto
{
Adjustments = new List<StrategicOpportunityGLAdjustment> { new() { NodeKey = "101", GoalAmountValue = 5000m } },
Rows = new List<string>()
};
var result = await _controller.UpdateGLDetails(1, dto, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityGLDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityGLAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateGLDetails_ReturnsOk_AndCallsService_WhenDtoIsValid()
{
const long opportunityId = 42;
var dto = new UpdateGLDetailsDto
{
Adjustments = new List<StrategicOpportunityGLAdjustment> { new() { NodeKey = "101", GoalAmountValue = 5000m } },
Rows = new List<string> { "system", "entity", "glAccount" }
};
var result = await _controller.UpdateGLDetails(opportunityId, dto, CancellationToken.None);
result.Should().BeOfType<OkResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityGLDetailsData(
opportunityId, dto.Adjustments, dto.Rows, CancellationToken.None), Times.Once);
}
// --- Route attribute assertions ---
[Test]
public void Controller_HasExpectedRouteTemplate()
{
var routeAttr = typeof(StrategicGeneralLedgerController).GetCustomAttribute<RouteAttribute>();
routeAttr.Should().NotBeNull();
routeAttr.Template.Should().Be("api/v{api-version:apiVersion}/[controller]");
}
[TestCase(nameof(StrategicGeneralLedgerController.GetOpportunityGL), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicGeneralLedgerController.CreateOpportunityGeneralLedger), typeof(HttpPostAttribute), null)]
[TestCase(nameof(StrategicGeneralLedgerController.UpdateOpportunityGeneralLedger), typeof(HttpPutAttribute), "{opportunityId}")]
[TestCase(nameof(StrategicGeneralLedgerController.DeleteOpportunityGeneralLedger), typeof(HttpDeleteAttribute), "{opportunityId}")]
[TestCase(nameof(StrategicGeneralLedgerController.GetGLDetail), typeof(HttpPostAttribute), "{opportunityId}/detail")]
[TestCase(nameof(StrategicGeneralLedgerController.GetGLTrackingDetail), typeof(HttpPostAttribute), "{opportunityId}/tracking-detail")]
[TestCase(nameof(StrategicGeneralLedgerController.UpdateGLDetails), typeof(HttpPutAttribute), "{opportunityId}/details")]
public void ActionRoute_HasExpectedHttpMethodAndTemplate(string methodName, Type httpAttrType, string expectedTemplate)
{
var method = typeof(StrategicGeneralLedgerController).GetMethod(methodName);
method.Should().NotBeNull($"method '{methodName}' should exist on {nameof(StrategicGeneralLedgerController)}");
var attr = method.GetCustomAttribute(httpAttrType);
attr.Should().NotBeNull($"{methodName} should be decorated with [{httpAttrType.Name}]");
var template = (string)attr.GetType().GetProperty("Template").GetValue(attr);
template.Should().Be(expectedTemplate);
}
}
}
@@ -0,0 +1,326 @@
using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.Controllers;
using Strata.ContinuousImprovement.Biz.StrategicOpportunities;
using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers
{
[ExcludeFromCodeCoverage]
[TestFixture, Category("Unit"), Category("Controller")]
public class StrategicOpportunityControllerTests
{
private StrategicOpportunityController _controller;
private Mock<IStrategicOpportunityService> _mockOpportunityService;
[SetUp]
public void SetUp()
{
_mockOpportunityService = new Mock<IStrategicOpportunityService>();
_controller = new StrategicOpportunityController(_mockOpportunityService.Object);
}
// --- GetGeneralInfo ---
[Test]
public async Task GetGeneralInfo_ReturnsOk_WhenOpportunityExists()
{
const long opportunityId = 1;
var info = new StrategicOpportunityGeneralInfo();
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityGeneralInfo(opportunityId, It.IsAny<CancellationToken>()))
.ReturnsAsync(info);
var result = await _controller.GetGeneralInfo(opportunityId, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(info);
}
[Test]
public async Task GetGeneralInfo_ReturnsNotFound_WhenOpportunityMissing()
{
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityGeneralInfo(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityGeneralInfo)null);
var result = await _controller.GetGeneralInfo(99, CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- GetDrawerDetails ---
[Test]
public async Task GetDrawerDetails_ReturnsOk_WhenOpportunityExists()
{
const long opportunityId = 5;
var details = new StrategicOpportunityDrawerDetails();
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityDrawerDetails(opportunityId, It.IsAny<CancellationToken>()))
.ReturnsAsync(details);
var result = await _controller.GetDrawerDetails(opportunityId, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(details);
}
[Test]
public async Task GetDrawerDetails_ReturnsNotFound_WhenOpportunityMissing()
{
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityDrawerDetails(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityDrawerDetails)null);
var result = await _controller.GetDrawerDetails(99, CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- Copy ---
[Test]
public async Task Copy_ReturnsOk_WithNewId_WhenSuccessful()
{
const long opportunityId = 10;
const long newId = 42;
_mockOpportunityService
.Setup(s => s.CopyStrategicOpportunity(opportunityId, It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(newId);
var result = await _controller.Copy(opportunityId, "Copy of Opp", CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(newId);
}
[Test]
public async Task Copy_ReturnsBadRequest_WhenServiceReturnsZero()
{
_mockOpportunityService
.Setup(s => s.CopyStrategicOpportunity(It.IsAny<long>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0L);
var result = await _controller.Copy(10, "Copy of Opp", CancellationToken.None);
result.Result.Should().BeOfType<BadRequestResult>();
}
// --- SetStatus ---
[Test]
public async Task SetStatus_ReturnsOk_WhenUpdateSucceeds()
{
const long opportunityId = 3;
var dto = new StrategicOpportunityUpdateStatusDto();
_mockOpportunityService
.Setup(s => s.UpdateStrategicOpportunityStatus(opportunityId, dto, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
var result = await _controller.SetStatus(opportunityId, dto, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
}
[Test]
public async Task SetStatus_ReturnsNotFound_WhenUpdateFails()
{
_mockOpportunityService
.Setup(s => s.UpdateStrategicOpportunityStatus(It.IsAny<long>(), It.IsAny<StrategicOpportunityUpdateStatusDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await _controller.SetStatus(99, new StrategicOpportunityUpdateStatusDto(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- UpdateLayout ---
[Test]
public async Task UpdateLayout_ReturnsOk_WhenUpdateSucceeds()
{
const long opportunityId = 7;
var layout = new StrategicOpportunityLayout();
_mockOpportunityService
.Setup(s => s.UpdateStrategicOpportunityLayout(opportunityId, layout, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
var result = await _controller.UpdateLayout(opportunityId, layout, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
}
[Test]
public async Task UpdateLayout_ReturnsNotFound_WhenUpdateFails()
{
_mockOpportunityService
.Setup(s => s.UpdateStrategicOpportunityLayout(It.IsAny<long>(), It.IsAny<StrategicOpportunityLayout>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await _controller.UpdateLayout(99, new StrategicOpportunityLayout(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- UpdateRoles ---
[Test]
public async Task UpdateRoles_ReturnsOk_WhenUpdateSucceeds()
{
const long opportunityId = 4;
var dto = new StrategicRolesUpdate();
_mockOpportunityService
.Setup(s => s.UpdateRoles(opportunityId, dto, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
var result = await _controller.UpdateRoles(opportunityId, dto, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
}
[Test]
public async Task UpdateRoles_ReturnsNotFound_WhenUpdateFails()
{
_mockOpportunityService
.Setup(s => s.UpdateRoles(It.IsAny<long>(), It.IsAny<StrategicRolesUpdate>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await _controller.UpdateRoles(99, new StrategicRolesUpdate(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- RunTrackingTask ---
[Test]
public async Task RunTrackingTask_ReturnsOk_WhenSuccessful()
{
const long opportunityId = 8;
_mockOpportunityService
.Setup(s => s.RunTrackingTask(opportunityId, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
var result = await _controller.RunTrackingTask(opportunityId, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
}
[Test]
public async Task RunTrackingTask_ReturnsNotFound_WhenOpportunityMissing()
{
_mockOpportunityService
.Setup(s => s.RunTrackingTask(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await _controller.RunTrackingTask(99, CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- GetMeasures ---
[Test]
public async Task GetMeasures_ReturnsOk_WhenMeasuresExist()
{
const long opportunityId = 2;
var measures = new List<StrategicOpportunityMeasureDto> { new() };
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityMeasures(opportunityId, It.IsAny<CancellationToken>()))
.ReturnsAsync(measures);
var result = await _controller.GetMeasures(opportunityId, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(measures);
}
[Test]
public async Task GetMeasures_ReturnsNotFound_WhenNoMeasures()
{
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityMeasures(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<StrategicOpportunityMeasureDto>());
var result = await _controller.GetMeasures(99, CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- CompleteInitiative ---
[Test]
public async Task CompleteInitiative_ReturnsOk_WhenSuccessful()
{
const long opportunityId = 6;
var data = new StrategicOpportunityCompleteInitiativeData();
_mockOpportunityService
.Setup(s => s.CompleteInitiative(opportunityId, data, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
var result = await _controller.CompleteInitiative(opportunityId, data, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
}
[Test]
public async Task CompleteInitiative_ReturnsNotFound_WhenOpportunityMissing()
{
_mockOpportunityService
.Setup(s => s.CompleteInitiative(It.IsAny<long>(), It.IsAny<StrategicOpportunityCompleteInitiativeData>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await _controller.CompleteInitiative(99, new StrategicOpportunityCompleteInitiativeData(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- Route attribute assertions ---
[Test]
public void Controller_HasExpectedRouteTemplate()
{
var routeAttr = typeof(StrategicOpportunityController).GetCustomAttribute<RouteAttribute>();
routeAttr.Should().NotBeNull();
routeAttr.Template.Should().Be("api/v{api-version:apiVersion}/[controller]");
}
[TestCase(nameof(StrategicOpportunityController.GetOpportunities), typeof(HttpGetAttribute), "")]
[TestCase(nameof(StrategicOpportunityController.GetGeneralInfo), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.IsNameValid), typeof(HttpPostAttribute), "[action]")]
[TestCase(nameof(StrategicOpportunityController.GetLayout), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.UpdateLayout), typeof(HttpPutAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.SetStatus), typeof(HttpPutAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.GetInitiatives), typeof(HttpGetAttribute), "[action]")]
[TestCase(nameof(StrategicOpportunityController.UpdateInitiative), typeof(HttpPutAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.UpdateRoles), typeof(HttpPutAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.UpdateRampUps), typeof(HttpPutAttribute), "{opportunityId}/ramp-ups")]
[TestCase(nameof(StrategicOpportunityController.GetRampUps), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.RunTrackingTask), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.CompleteInitiative), typeof(HttpPutAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.GetMeasures), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.GetPrimaryMeasure), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.GetMeasureDetails), typeof(HttpGetAttribute), "{measureId}/[action]")]
[TestCase(nameof(StrategicOpportunityController.GetDrawerDetails), typeof(HttpGetAttribute), "{opportunityId}")]
[TestCase(nameof(StrategicOpportunityController.Copy), typeof(HttpPostAttribute), "{opportunityId}/copy")]
public void ActionRoute_HasExpectedHttpMethodAndTemplate(string methodName, Type httpAttrType, string expectedTemplate)
{
var method = typeof(StrategicOpportunityController).GetMethod(methodName);
method.Should().NotBeNull($"method '{methodName}' should exist on {nameof(StrategicOpportunityController)}");
var attr = method.GetCustomAttribute(httpAttrType);
attr.Should().NotBeNull($"{methodName} should be decorated with [{httpAttrType.Name}]");
var template = (string)attr.GetType().GetProperty("Template").GetValue(attr);
template.Should().Be(expectedTemplate);
}
}
}
@@ -0,0 +1,20 @@
using Riok.Mapperly.Abstractions;
using Strata.ContinuousImprovement.Biz.Dtos;
using Strata.ContinuousImprovement.Biz.Network;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Generics
{
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)]
public static partial class ClientNetworkOpportunityMapper
{
public static ClientNetworkOpportunityDto MapToDto(this ClientNetworkOpportunity src)
{
var mapped = src.Mapped();
return mapped;
}
private static partial ClientNetworkOpportunityDto Mapped(this ClientNetworkOpportunity src);
}
}
@@ -0,0 +1,13 @@
using Riok.Mapperly.Abstractions;
using Strata.ContinuousImprovement.Biz.OpportunityComments;
using static Strata.ContinuousImprovement.Api.Controllers.CommentsController;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Api.Generics
{
[Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)]
public static partial class CommentMapper
{
public static partial Comment Map(this NewComment src);
}
}
@@ -0,0 +1,24 @@
using Hangfire;
using Hangfire.Common;
using Hangfire.States;
using System;
using System.Collections.Generic;
namespace Strata.ContinuousImprovement.Api.Test.Unit.Generics
{
public class FakeBackgroundJobClient : IBackgroundJobClient
{
public Queue<Job> Jobs { get; } = new Queue<Job>();
public string Create(Job job, IState state)
{
Jobs.Enqueue(job);
return Jobs.Count.ToString();
}
public bool ChangeState(string jobId, IState state, string expectedState)
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,76 @@
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Api.SnowflakeControllers;
using Strata.DecisionSupport.Biz.Mapperly;
using Strata.DecisionSupport.Biz.Services;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using VerifyNUnit;
using VerifyTests;
using Encounters = Strata.DecisionSupport.Biz.Encounters;
using Parameters = Strata.DecisionSupport.Biz.Parameters;
namespace Strata.ContinuousImprovement.Api.Test.Unit.SnowflakeControllers
{
[ExcludeFromCodeCoverage]
[TestFixture, Category("Unit"), Category("Controller")]
public class UtilizationVariationControllerTest
{
public VerifySettings verifySettings;
private UtilizationVariationController controller;
[SetUp]
public void SetUp()
{
var charges = new List<Encounters.ChargeInfo> {
new Encounters.ChargeInfo()
};
var jazzCharges = charges.Select(c => JazzEncounterInfoMapper.MapToJazz(c));
var service = Mock.Of<UtilizationVariationService>();
controller = new UtilizationVariationController(service);
verifySettings = TestExtensions.TestSettings();
}
[Test]
public async Task TestController()
{
var controllerExists = controller != null;
await Verifier.Verify(controllerExists, verifySettings)
.UseFileName(TestContext.CurrentContext.TestName());
}
[TestCaseSource(nameof(GetChargeInfoTestCases))]
public async Task TestGetChargeInfo(Guid opportunityGuid, IEnumerable<int> chargeCodeIds)
{
var queryInfo = new Parameters.ChargeQueryInfo(Guid.NewGuid(), 10, chargeCodeIds.ToList(), opportunityGuid);
var response = await controller.GetChargeInfoAsync(queryInfo, CancellationToken.None);
await Verifier.Verify(response, verifySettings)
.UseFileName(TestContext.CurrentContext.TestName());
}
[TestCaseSource(nameof(GetChargeInfoTestCases))]
public async Task TestGetAllChargeInfo(Guid opportunityGuid, IEnumerable<int> chargeCodeIds)
{
var queryInfo = new Parameters.AllChargeQueryInfo(opportunityGuid, chargeCodeIds.ToList());
var response = await controller.GetAllChargeInfoAsync(queryInfo, CancellationToken.None);
await Verifier.Verify(response, verifySettings)
.UseFileName(TestContext.CurrentContext.TestName());
}
public static IEnumerable<TestCaseData> GetChargeInfoTestCases()
{
yield return new TestCaseData(Guid.Empty, Enumerable.Empty<int>())
.SetName("{m} Empty OpportunityGuid");
yield return new TestCaseData(Guid.Empty, new[] { 1 })
.SetName("{m} with ChargeCodes");
yield return new TestCaseData(Guid.NewGuid(), new[] { 1 })
.SetName("{m} with Opportunity and ChargeCodes");
}
}
}
@@ -0,0 +1,78 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Configuration\**" />
<Compile Remove="Resource\Examples\AllOpportunity\**" />
<Compile Remove="Utilities\**" />
<EmbeddedResource Remove="Configuration\**" />
<EmbeddedResource Remove="Resource\Examples\AllOpportunity\**" />
<EmbeddedResource Remove="Utilities\**" />
<None Remove="Configuration\**" />
<None Remove="Resource\Examples\AllOpportunity\**" />
<None Remove="Utilities\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Biz\All\OpportunityType.cs" />
<Compile Remove="Biz\Generics\ConfigurationServiceTest.cs" />
<Compile Remove="Biz\Generics\DbSetExtensions.cs" />
<Compile Remove="Biz\Generics\VariationOpportunityServiceTest.cs" />
<Compile Remove="Biz\SlugifyParameterTransformerTest.cs" />
<Compile Remove="Biz\StaffingToDemand\Opportunities\StaffingToDemandOpportunityServiceTest.cs" />
<Compile Remove="Biz\UtilizationVariation\Opportunities\VariationOpportunityFilter.cs" />
<Compile Remove="Generics\OpportunityTypeExtensions.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="Resource\Examples\ExcelFilter.xlsx" />
<None Remove="Resource\PRExcelFilter.xlsx" />
</ItemGroup>
<ItemGroup>
<None Include="Strata - Backup.ContinuousImprovement.Test.Unit.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ClosedXML.Signed" Version="0.95.4" />
<PackageReference Include="coverlet.msbuild" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.10" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Remotion.Linq.Development" Version="2.2.0" />
<PackageReference Include="Talentsoft.Moq.SetupAsync" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Strata.ContinuousImprovement.Biz\Strata.ContinuousImprovement.Biz.csproj" />
<ProjectReference Include="..\..\src\Strata.ContinuousImprovement.Api\Strata.ContinuousImprovement.Api.csproj" />
<ProjectReference Include="..\Strata.ContinuousImprovement.Biz.Test.Unit\Strata.ContinuousImprovement.Biz.Test.Unit.csproj" />
<ProjectReference Include="..\Strata.ContinuousImprovement.Biz.Test.Unit\Strata.ContinuousImprovement.Biz.Test.Unit.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resource\Examples\AllOpportunity\" />
<Folder Include="Resource\Examples\EncounterOpportunity\" />
<Folder Include="Resource\Examples\FreeFormOpportunity\" />
<Folder Include="Resource\Examples\GeneralLedgerOpportunity\" />
<Folder Include="Resource\Examples\PayrollOpportunity\" />
<Folder Include="Resource\Examples\QualityVariationItemOpportunity\" />
<Folder Include="Resource\Examples\StaffingToDemandOpportunity\" />
<Folder Include="Resource\Examples\VariationOpportunity\" />
<Folder Include="SnowflakeControllers\snapshots\" />
</ItemGroup>
</Project>
@@ -0,0 +1,47 @@
using NUnit.Framework;
using System;
using System.Text.RegularExpressions;
using VerifyTests;
namespace Strata.ContinuousImprovement.Api.Test.Unit
{
public static class TestExtensions
{
/// <summary>
/// The test name
/// </summary>
/// <param name="context"></param>
/// <returns>
/// The name of the test split by camel case for a max length of 200 characters.
/// </returns>
public static string TestName(this TestContext context)
{
var testName = context.Test.Name.SplitCamelCase();
testName = testName[..Math.Min(200, testName.Length)];
return testName;
}
public static string SplitCamelCase(this string input)
{
if (string.IsNullOrEmpty(input)) { return input; }
const string pattern = "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-r + t-z])|[A-Z](?=[A-Z][s][a-z]))";
if (input.StartsWith("FY", StringComparison.Ordinal))
{ // hack for FY2012, etc
return "FY " + Regex.Replace(input.Remove(0, 2), pattern, "$1 ");
}
input = Regex.Replace(input, @"\{|\}|:|,|\)|\(", "", RegexOptions.Singleline | RegexOptions.IgnoreCase);
return Regex.Replace(input, pattern, "$1 ");
}
public static VerifySettings TestSettings()
{
var settings = new VerifySettings();
settings.UseDirectory("snapshots");
settings.ScrubInlineGuids();
return settings;
}
}
}
@@ -0,0 +1,5 @@
{
"configProperties": {
"System.Drawing.EnableUnixSupport": true
}
}
@@ -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);
}
}
}
@@ -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));
}
}
}
@@ -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());
}
}
}
@@ -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;
}
}
}
}
@@ -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""}]}"
};
}
}
}
@@ -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)
};
}
}
}
@@ -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;
}
}
}
@@ -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();
}
}
}
@@ -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)]
}
}
}
};
}
}
@@ -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");
}
}
}
@@ -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");
}
}
}
@@ -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)),
]
});
}
}
}
@@ -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");
}
}
}
@@ -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
";
}
}
@@ -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
";
}
}
@@ -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>());
}
}
}
@@ -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>
@@ -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"
}
}
@@ -0,0 +1,642 @@
using FluentAssertions;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.All.Opportunities;
using Strata.ContinuousImprovement.Biz.All.Opportunities.Filters;
using Strata.ContinuousImprovement.Biz.Configuration;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.Encounter;
using Strata.ContinuousImprovement.Biz.Filter;
using Strata.ContinuousImprovement.Biz.Generics;
using Strata.ContinuousImprovement.Biz.Initiatives;
using Strata.ContinuousImprovement.Biz.Pagination;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.Hangfire.Jazz.Client;
using Strata.Hangfire.Jazz.Client.Models;
using Strata.Schema.Client;
using Strata.Schema.Client.Dtos;
using Strata.Schema.Client.Dtos.Info;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.All.Opportunities
{
[TestFixture, Category("Unit")]
public class AllOpportunityServiceTest
{
private IAllOpportunityService _allOpportunityService;
private Mock<IAsyncDbContextFactory<JazzDbContext>> _dbContextFactoryMock;
private Mock<IJazzDbContext> _jazzDbContextMock;
private Mock<ISchemaServiceClient> _schemaServiceMock;
private Mock<IJazzHangfireServiceClient> _hangfireServiceMock;
private Mock<IConfigurationService> _configurationMock;
private IInitiativeService _initiativeService;
private IUpdateFactOpportunitySavingsService _updateFactOpportunitySavingsService;
private static readonly ResourceFileExtractor _extractor = new ResourceFileExtractor(".Resource.");
private List<Biz.Configuration.Configuration> _configurations;
internal static List<Guid> ConfigGuids = new() { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
internal static List<string> ConfigNames = new() { "FY2021", "FY2022", "FY2023", "FY2024" };
private ScoreDimensionInfoDto _dimension;
private List<ScoreHierarchyDto> _hierarchies;
private List<HierarchyNodeDto> _hierarchyNodes;
private List<FilterHelper> _filterHelpers;
private JobEnqueuedResponse _jobResponse;
[SetUp]
public void TestSetUp()
{
_dbContextFactoryMock = new Mock<IAsyncDbContextFactory<JazzDbContext>>();
_jazzDbContextMock = new Mock<IJazzDbContext>();
_schemaServiceMock = new Mock<ISchemaServiceClient>();
_hangfireServiceMock = new Mock<IJazzHangfireServiceClient>();
_configurationMock = new Mock<IConfigurationService>();
_initiativeService = Mock.Of<IInitiativeService>();
_updateFactOpportunitySavingsService = Mock.Of<IUpdateFactOpportunitySavingsService>();
Mock.Get(_initiativeService)
.Setup(s => s.DeleteAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
Mock.Get(_updateFactOpportunitySavingsService)
.Setup(s => s.UpdateFactOpportunitySavings(It.IsAny<CancellationToken>()));
_allOpportunityService = new AllOpportunityService(_dbContextFactoryMock.Object, null, null, _initiativeService, _updateFactOpportunitySavingsService);
_configurations = new List<Biz.Configuration.Configuration>
{
new()
{
ConfigurationGuid = ConfigGuids[0],
Name = ConfigNames.First(),
DepartmentRollupGlobalId = string.Empty,
ServiceLineDimensionGuid = Guid.NewGuid()
},
new()
{
ConfigurationGuid = ConfigGuids[1],
Name = ConfigNames[1],
DepartmentRollupGlobalId = "Department",
ServiceLineDimensionGuid = Guid.NewGuid()
},
new()
{
ConfigurationGuid = ConfigGuids[2],
Name = ConfigNames[2],
DepartmentRollupGlobalId = string.Empty,
ServiceLineDimensionGuid = Guid.NewGuid()
},
new()
{
ConfigurationGuid = ConfigGuids[3],
Name = ConfigNames.Last(),
DepartmentRollupGlobalId = string.Empty,
ServiceLineDimensionGuid = Guid.NewGuid()
}
};
_dimension = new ScoreDimensionInfoDto
{
FriendlyName = "test",
DimensionGuid = Guid.NewGuid(),
DefaultHierarchyGuid = Guid.NewGuid()
};
_hierarchies = new List<ScoreHierarchyDto>
{
new ScoreHierarchyDto
{
FriendlyName = "test",
HierarchyGuid = _dimension.DefaultHierarchyGuid
}
};
_hierarchyNodes = new List<HierarchyNodeDto>
{
new HierarchyNodeDto
{
Name = "Case Type",
Path = "CaseType|CaseType|1"
}
};
_jobResponse = new JobEnqueuedResponse
{
JobId = Guid.NewGuid()
};
_filterHelpers = new List<FilterHelper>
{
new FilterHelper
{
Text = "Case Type",
Id = "CaseType|CaseType|1"
}
};
_hangfireServiceMock.Setup(moq =>
moq.EnqueueJobAsync(It.IsAny<EnqueueJobDto>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_jobResponse));
_configurationMock.Setup(moq => moq.GetConfigurationAsync(It.Is<Guid>(c => c == ConfigGuids[0]), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_configurations[0]));
_configurationMock.Setup(moq => moq.GetConfigurationAsync(It.Is<Guid>(c => c == ConfigGuids[1]), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_configurations[1]));
_configurationMock.Setup(moq => moq.GetConfigurationAsync(It.Is<Guid>(c => c == ConfigGuids[2]), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_configurations[2]));
_configurationMock.Setup(moq => moq.GetConfigurationAsync(It.Is<Guid>(c => c == _configurations.Last().ConfigurationGuid), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_configurations.Last()));
_schemaServiceMock.Setup(moq => moq.GetDimensionByGlobalIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_dimension));
_schemaServiceMock.Setup(moq => moq.GetDimensionByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_dimension));
_schemaServiceMock.Setup(moq => moq.GetScoreHierarchiesByGlobalIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_hierarchies.AsEnumerable()));
_schemaServiceMock.Setup(moq => moq.GetHierarchyNodesByHierarchyGuidAsync(It.IsAny<Guid>(),
It.IsAny<HierarchyNodeParametersDto>(), It.IsAny<List<ExtraFilterDto>>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_hierarchyNodes.AsEnumerable()));
}
[Test]
public async Task TestFirstPage_Sorted_Asc()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var allOpportunityService = new AllOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
var filters = new AllFilters()
{
OpportunityTypes = new List<string>(),
IdentifiedSavings = 0,
ViewVisible = true,
ExcludeInitiatives = false,
OpportunitySearch = "",
ConfigurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid
};
var pagingOptions = new PagingOptions("IdentifiedSavings", SortOrder.Asc);
// act
var taskResult = await allOpportunityService
.GetPagedAsync(pagingOptions, filters, CancellationToken.None);
// assert
taskResult.Data.Should().NotBeNullOrEmpty()
.And.AllBeOfType<AllOpportunity>()
.And.HaveCountGreaterThan(1);
taskResult.Data.First().IdentifiedSavings
.Should().BeLessThan(taskResult.Data.Last().IdentifiedSavings);
}
[Test]
public async Task TestFirstPage_Sorted_Desc()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var allOpportunityService = new AllOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
var filters = new AllFilters()
{
OpportunityTypes = new List<string>(),
IdentifiedSavings = 0,
ViewVisible = true,
ExcludeInitiatives = false,
OpportunitySearch = "",
ConfigurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid
};
var pagingOptions = new PagingOptions("IdentifiedSavings", SortOrder.Desc);
// act
var taskResult = await allOpportunityService
.GetPagedAsync(pagingOptions, filters, CancellationToken.None);
// assert
taskResult.Data.Should().NotBeNullOrEmpty()
.And.AllBeOfType<AllOpportunity>()
.And.HaveCountGreaterThan(1);
taskResult.Data.First().IdentifiedSavings
.Should().BeGreaterThan(taskResult.Data.Last().IdentifiedSavings);
}
[TestCaseSource(nameof(GetFilterData))]
public void TestValidateFilters(AllFilters filters)
{
if (filters == null) return;
if (filters.OpportunityTypes.Any())
filters.OpportunityTypes.Should()
.AllBeOfType<string>()
.And.NotBeNullOrEmpty();
}
[Test]
public async Task TestGetExcelWorkbook()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var opp = framework.ENOpportunities.First();
var filters = new AllFilters()
{
ConfigurationGuid = framework.Configurations.First().ConfigurationGuid,
OpportunityTypes = new[] {$"{(int)OpportunityTypes.Variation}",
$"{(int)OpportunityTypes.QualityVariation}",
$"{(int)OpportunityTypes.FreeForm}",
$"{(int)OpportunityTypes.GeneralLedger}",
$"{(int)OpportunityTypes.Payroll}",
$"{(int)OpportunityTypes.Encounter}" },
};
var service = new AllOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var wb = await service.ExportAsync(new SortOptions(), filters, CancellationToken.None);
#pragma warning disable S125 // Sections of code should not be commented out
//wb.SaveAs(System.IO.Path.Combine(@"C:\Git\continuousimprovement\tests\Strata.ContinuousImprovement.Biz.Test.Unit\Resource", "AllExcelFilter.xlsx"));
#pragma warning restore S125 // Sections of code should not be commented out
// assert
wb.Worksheets.Should().HaveCount(1);
var ws = wb.Worksheet(1);
ws.Should().NotBeNull();
var expected = @"Examples.AllOpportunity.ExcelFilter.xlsx";
using (var expectedStream = _extractor.ReadFileFromResourceToStream(expected))
using (var actualStream = new MemoryStream())
{
wb.SaveAs(actualStream);
actualStream.Compare(expectedStream, out var message).Should().BeTrue(message);
}
}
[Test]
public async Task TestGetExcelWorkbookNullFilter()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var service = new AllOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var wb = await service.ExportAsync(null, null, CancellationToken.None);
#pragma warning disable S125 // Sections of code should not be commented out
//wb.SaveAs(System.IO.Path.Combine(@"C:\Git\continuousimprovement\tests\Strata.ContinuousImprovement.Biz.Test.Unit\Resource", "AllExcelNullFilter.xlsx"));
#pragma warning restore S125 // Sections of code should not be commented out
// assert
wb.Worksheets.Should().HaveCount(1);
var ws = wb.Worksheet(1);
ws.Should().NotBeNull();
var expected = @"Examples.AllOpportunity.ExcelNullFilter.xlsx";
using (var expectedStream = _extractor.ReadFileFromResourceToStream(expected))
using (var actualStream = new MemoryStream())
{
wb.SaveAs(actualStream);
actualStream.Compare(expectedStream, out var message).Should().BeTrue(message);
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestGetFilterOptionsAsync(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var opportunityService = new AllOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
// act
var taskResult = await opportunityService
.GetFilterOptionsAsync(new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345"), cancellationToken);
// assert
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName))
{
taskResult.Should().BeNull();
return;
}
taskResult.Should()
.BeOfType<AllFilterOptions>()
.And.NotBeNull();
taskResult.OpportunityTypes.Should().AllBeOfType<FilterMember>().And.NotBeNullOrEmpty()
.And.NotContain(cd => string.IsNullOrEmpty(cd.Id))
.And.NotContain(cd => string.IsNullOrEmpty(cd.Text));
}
catch (NotImplementedException nie)
{
nie.Should().BeOfType<NotImplementedException>();
}
catch (AssertionException ae)
{
ae.Should().BeOfType<AssertionException>();
}
catch (Exception e)
{
e.Should().BeOfType<OperationCanceledException>();
}
}
[Test, Ignore("Handled by the other services")]
public async Task TestSetHidden()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var opportunities = framework.ENOpportunities;
var opportunity = opportunities.First(vo => !vo.IsHidden);
var isHidden = opportunity.IsHidden;
var service = new AllOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
await service.SetHiddenAsync(opportunity.OpportunityGuid, !isHidden, CancellationToken.None);
// assert
var result = await service.GetById(opportunity.OpportunityGuid, CancellationToken.None);
result.IsHidden.Should().Be(!isHidden);
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestSetHiddenAsync(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var allOpportunityService = new AllOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
var opportunities = allOpportunityService.GetAllAsync(cancellationToken).Result;
var opportunity = opportunities.FirstOrDefault(o => o.IsHidden);
if (opportunity == null) return;
var isHidden = opportunity.IsHidden;
//act
await allOpportunityService.SetHiddenAsync(opportunity.OpportunityGuid, !isHidden,
cancellationToken);
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName)) return;
var result = allOpportunityService.GetById(opportunity.OpportunityGuid, cancellationToken).Result;
result.IsHidden.Should().Be(!isHidden);
}
catch (Exception e)
{
switch (e.GetType().Name)
{
case "AggregateException":
case "OperationCanceledException":
return;
}
e.Should().NotBeOfType<Exception>();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestSetHiddenAsync_NoOpportunity(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var opportunity = jazzEntityFrameworkFactory.ENOpportunities.FirstOrDefault(o => o.IsHidden);
if (opportunity == null) return;
var isHidden = opportunity.IsHidden;
var allOpportunityService = new AllOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
// act
await allOpportunityService.SetHiddenAsync(Guid.Empty, !isHidden,
cancellationToken);
}
catch (Exception e)
{
e.Should().BeOfType<OperationCanceledException>();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestSetNoteAsync(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var allOpportunityService = new AllOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
var opportunities = allOpportunityService.GetAllAsync(cancellationToken).Result;
var opportunity = opportunities.FirstOrDefault(o => o.Note == string.Empty);
if (opportunity == null) return;
var note = "new test note";
// act
await allOpportunityService.SetNoteAsync(opportunity.OpportunityGuid, note,
CancellationToken.None);
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName)) return;
var result = allOpportunityService.GetById(opportunity.OpportunityGuid, cancellationToken).Result;
result.Note.Should().Be(note);
// assert
}
catch (Exception e)
{
switch (e.GetType().Name)
{
case "AggregateException":
case "OperationCanceledException":
return;
}
e.Should().NotBeOfType<Exception>();
}
}
[Test, Ignore("Handled by the other services")]
public async Task TestSetNote()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var opportunities = framework.ENOpportunities;
var opportunity = opportunities.First(vo => string.IsNullOrEmpty(vo.Note));
var note = opportunity.Note;
var expected = "Now is the time for all good engineers to write great code!";
var service = new AllOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
await service.SetNoteAsync(opportunity.OpportunityGuid, expected, CancellationToken.None);
// assert
var result = await service.GetById(opportunity.OpportunityGuid, CancellationToken.None);
result.Note.Should().NotBeNullOrEmpty()
.And.NotBe(note)
.And.Be(expected);
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestSetNoteAsync_NoOpportunity(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var opportunity = jazzEntityFrameworkFactory.ENOpportunities.FirstOrDefault(o => o.Note == string.Empty);
if (opportunity == null) return;
var note = "new test note";
var allOpportunityService = new AllOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
// act
await allOpportunityService.SetNoteAsync(Guid.Empty, note, cancellationToken);
// assert
}
catch (Exception e)
{
e.Should().BeOfType<OperationCanceledException>();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworks))]
public async Task TestGetAllAsync(JazzEntityFrameworkFactory jazzEntityFrameworkFactory)
{
// arrange
var allOpportunityService = new AllOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var taskResult = await allOpportunityService.GetAllAsync(CancellationToken.None);
// assert
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName))
{
taskResult.Should().BeNullOrEmpty();
}
else
{
taskResult.Should().NotBeNullOrEmpty();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworks))]
public async Task TestGetById(JazzEntityFrameworkFactory jazzEntityFrameworkFactory)
{
// arrange
var allOpportunityService = new AllOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var tempResult = await allOpportunityService.GetAllAsync(CancellationToken.None);
var taskResult =
await allOpportunityService.GetById(tempResult.FirstOrDefault()?.OpportunityGuid ?? Guid.Empty, CancellationToken.None);
// assert
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName))
{
taskResult.Should().BeNull();
}
else
{
taskResult.Should().NotBeNull()
.And.BeOfType<AllOpportunity>();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworks))]
public async Task TestGetByIds(JazzEntityFrameworkFactory jazzEntityFrameworkFactory)
{
// arrange
var allOpportunityService = new AllOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var tempResult = await allOpportunityService.GetAllAsync(CancellationToken.None);
var ids = tempResult.Select(o => o.OpportunityGuid);
var taskResult = await allOpportunityService.GetByIds(ids, CancellationToken.None);
// assert
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName))
{
taskResult.Should().BeNullOrEmpty();
}
else
{
taskResult.Should().NotBeNullOrEmpty()
.And.AllBeOfType<AllOpportunity>()
.And.HaveCount(ids.Count());
}
}
[TearDown]
public void TestTearDown()
{
// Method intentionally left empty.
}
#region TestCaseData
public static IEnumerable<TestCaseData> GetFilterData()
{
yield return new TestCaseData(new AllFilters { ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("No Filters");
yield return new TestCaseData(new AllFilters { ViewVisible = true, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("View Visible");
yield return new TestCaseData(new AllFilters { ViewVisible = false, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("Not View Visible");
yield return new TestCaseData(new AllFilters { IdentifiedSavings = 1999, ViewVisible = true, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName(
"By Identified Savings");
yield return new TestCaseData(new AllFilters { IdentifiedSavings = 999, ViewVisible = false, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName(
"By Identified Savings Not ViewVisible");
yield return new TestCaseData(new AllFilters { OpportunityTypes = new[] { "Entity" }, ViewVisible = true, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") })
.SetName("By OpportunityTypes");
yield return new TestCaseData(new AllFilters { OpportunityTypes = new[] { "Provider" }, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345"), ViewVisible = false })
.SetName("By OpportunityTypes Not ViewVisible");
yield return new TestCaseData(new AllFilters { OpportunitySearch = "EN - 101", ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("Search By Id");
yield return new TestCaseData(new AllFilters { ExcludeInitiatives = true, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("Exclude Opportunities with Initiatives");
}
//public static IEnumerable<TestCaseData> GetAllOpportunityDataModel()
//{
// return from configGuid in ConfigGuids
// from EncounterGroup encounterGroup in Enum.GetValues(typeof(EncounterGroup))
// where encounterGroup != EncounterGroup.Both
// from EncounterOpportunityLevel encounterOpportunityLevel in Enum.GetValues(typeof(EncounterOpportunityLevel))
// from encounterGroups in new[] { true, false }
// from OpportunityTypes in new[] { true, false }
// select new TestCaseData(new AllOpportunityDataModel
// {
// ConfigurationGuid = configGuid,
// EncounterGroup = (int)encounterGroup,
// EncounterOpportunityLevel = encounterOpportunityLevel,
// HasAllEncounterGroups = encounterGroups,
// CaseTypes = encounterGroup == EncounterGroup.CaseType
// ? new List<FilterHelper>
// {
// new() {Text = $"{encounterGroup}", Id = "Id"}
// }
// : new List<FilterHelper>(),
// PatientPopulations = encounterGroup == EncounterGroup.PatientPopulation
// ? new List<FilterHelper>
// {
// new () {Text = $"{encounterGroup}", Id = "Id"}
// }
// : new List<FilterHelper>(),
// HasAllOpportunityTypes = OpportunityTypes,
// Entities = encounterOpportunityLevel == EncounterOpportunityLevel.Entity
// ? new List<FilterHelper>
// {
// new () {Text = $"{encounterOpportunityLevel}", Id = "Id"}
// }
// : new List<FilterHelper>(),
// ServiceLines = encounterOpportunityLevel == EncounterOpportunityLevel.ServiceLine
// ? new List<FilterHelper>
// {
// new () {Text = $"{encounterOpportunityLevel}", Id = "Id"}
// }
// : new List<FilterHelper>(),
// Specialties = encounterOpportunityLevel == EncounterOpportunityLevel.Specialty
// ? new List<FilterHelper>
// {
// new () {Text = $"{encounterOpportunityLevel}", Id = "Id"}
// }
// : new List<FilterHelper>(),
// Providers = encounterOpportunityLevel == EncounterOpportunityLevel.Provider
// ? new List<FilterHelper>
// {
// new () {Text = $"{encounterOpportunityLevel}", Id = "Id"}
// }
// : new List<FilterHelper>()
// }).SetName($"{nameof(TestCreateAsync)} {ConfigNames[ConfigGuids.IndexOf(configGuid)]} IsUsingCustomEntityDim: {(ConfigGuids.IndexOf(configGuid) == 1)} IsUsingSg2: {(ConfigGuids.IndexOf(configGuid) == 3)} {encounterGroup} {encounterOpportunityLevel}");
//}
#endregion
}
}
@@ -0,0 +1,30 @@
using Strata.ContinuousImprovement.Biz.Generics;
namespace Strata.ContinuousImprovement.Test.Unit.Biz.All
{
public static class OpportunityTypesExtensions
{
public static string OpportunityTypeName(this OpportunityTypes opportunityType)
{
switch (opportunityType)
{
case OpportunityTypes.Variation:
return "Utilization Variation";
case OpportunityTypes.StaffingToDemand:
return "Staffing To Demand";
case OpportunityTypes.QualityVariation:
return "Quality Variation";
case OpportunityTypes.FreeForm:
return "Free Form";
case OpportunityTypes.GeneralLedger:
return "General Ledger";
case OpportunityTypes.Payroll:
return "Payroll";
case OpportunityTypes.Encounter:
return "Encounter";
default:
return "";
}
}
}
}
@@ -0,0 +1,253 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.ChargeCode;
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.Schema.Client.Dtos;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using VerifyNUnit;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.ChargeCode.Opportunities
{
[TestFixture()]
public class CCOpportunityServiceTests : OpportunityServiceTests
{
public IJazzDbContext DbContext { get; set; }
private IAsyncDbContextFactory<JazzDbContext> _dbContextFactory { get; set; }
public ICCOpportunityService CCOpportunityService { get; set; }
public IOptions<ChargeCodeOpportunityValidationSection> SectionAccessor { get; set; }
public ILogger<CCOpportunityService> Logger { get; set; }
[SetUp]
public void TestSetup()
{
var chargeCodeDimensionGuid = Guid.NewGuid();
var caseTypeDimensionGuid = Guid.NewGuid();
Mock.Get(SchemaServiceClient)
.Setup(s => s.GetDimensionByGlobalIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns<string, CancellationToken>((globalId, token) =>
globalId switch
{
"Charge Code" => Task.FromResult(new Schema.Client.Dtos.Info.ScoreDimensionInfoDto { DimensionGuid = chargeCodeDimensionGuid }),
"Case Type" => Task.FromResult(new Schema.Client.Dtos.Info.ScoreDimensionInfoDto { DimensionGuid = caseTypeDimensionGuid }),
_ => Task.FromResult(new Schema.Client.Dtos.Info.ScoreDimensionInfoDto { DimensionGuid = Guid.NewGuid() })
});
Mock.Get(SchemaServiceClient)
.Setup(x => x.GetHierarchyNodesFromHierarchyPathsAsync(It.IsAny<Guid>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
.Returns<Guid, IEnumerable<string>, CancellationToken>((dimensionGuid, codeHpaths, token)
=> Task.FromResult(new List<HierarchyNodeDto> {
new HierarchyNodeDto{
Path = "Path",
PathDisplay = "PathDisplay",
Name = "Name",
Count = 10,
IsLeaf= false
}
}.AsEnumerable()));
Mock.Get(SchemaServiceClient)
.Setup(x => x.GetDimensionMembersFromHierarchyPathsAsync(It.IsAny<Guid>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
.Returns<Guid, IEnumerable<string>, CancellationToken>((dimensionGuid, codeHpaths, token)
=> Task.FromResult(new List<MemberDto> {
new MemberDto
{
Name ="Name",
Id = "02"
}
}.AsEnumerable()));
Mock.Get(SchemaServiceClient)
.Setup(x => x.GetFilteredDimensionMembersByGlobalIdAsync<ChargeCodePickerModel>(It.IsAny<string>(), It.IsAny<List<int>>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(new List<ChargeCodePickerModel> {
new ChargeCodePickerModel(){
ChargeCodeId = 99,
Name = "TestName",
CostDriver = "TestCostDriver",
Rollup = "TestRollup"
}
}.AsEnumerable()));
var principal = TestUtilities.GetClaimsPrincipalAccessor();
var defaultUser = principal.GetCurrentClaimsPrincipal();
Mock.Get(ClaimsPrincipalAccessor)
.Setup(x => x.GetCurrentClaimsPrincipal())
.Returns(defaultUser);
SectionAccessor = Mock.Of<IOptions<ChargeCodeOpportunityValidationSection>>();
Mock.Get(SectionAccessor)
.Setup(x => x.Value)
.Returns(new ChargeCodeOpportunityValidationSection
{
MaxChargeCodeSelections = 10,
MaxOpportunityLevelSelections = 10,
});
Logger = Mock.Of<ILogger<CCOpportunityService>>();
CCOpportunityService = new CCOpportunityService(DbContextFactory,
null, // CentralDbContext,
ClaimsPrincipalAccessor,
ConfigurationService,
AuthorizationServiceClient,
IdServiceClient,
FiscalMonthResolver,
SchemaServiceClient,
BackgroundJobClient,
NotificationHubContext,
SectionAccessor,
InitiativeService,
SnowflakeDatabaseContext,
UpdateFactOpportunitySavingsService,
Logger);
}
[Test()]
public async Task CCOpportunityServiceTest()
{
await Verifier.Verify(CCOpportunityService, VerifySettings)
.UseFileName(TestContext.CurrentContext.TestName());
}
[Test()]
public async Task GetDbContextAsyncTest()
{
var dbContext = await CCOpportunityService.GetDbContext(CancellationToken.None);
var names = dbContext.AllOpportunities.Select(opp => $"All - {opp.Name} {opp.OpportunityId}").ToList();
names.AddRange(dbContext.ChargeCodeOpportunities.Select(opp => opp.Name ?? ""));
names.AddRange(dbContext.EncounterOpportunities.Select(opp => opp.Name ?? ""));
names.AddRange(dbContext.FFOpportunities.Select(opp => opp.Name ?? ""));
names.AddRange(dbContext.GeneralLedgerOpportunities.Select(opp => opp.Name ?? ""));
names.AddRange(dbContext.Initiatives.Select(opp => $"Initiative - {opp.Name} {opp.OpportunityId}"));
names.AddRange(dbContext.PayrollOpportunities.Select(opp => opp.Name ?? ""));
names.AddRange(dbContext.VariationOpportunities.Where(opp => opp.OpportunityKey.StartsWith("UV"))
.Select(opp => $"{opp.CostDriver ?? ""} {opp.ServiceLineName ?? ""} {opp.EntityName ?? ""} {opp.OpportunityId}"));
await Verifier.Verify(new
{
names = names.Distinct().OrderBy(x => x),
counts = new[] {
dbContext.Configurations.Count(),
dbContext.AllOpportunities.Count(opp => !opp.OpportunityKey.StartsWith("FE")),
dbContext.ChargeCodeOpportunities.Count(),
dbContext.EncounterOpportunities.Count(),
dbContext.FFOpportunities.Count(),
dbContext.GeneralLedgerOpportunities.Count(),
dbContext.Initiatives.Count(),
dbContext.PayrollOpportunities.Count(),
dbContext.VariationOpportunities.Count(opp => opp.OpportunityKey.StartsWith("UV"))
}
}, VerifySettings)
.IgnoreInstance<Guid>((x) => true)
.UseFileName(TestContext.CurrentContext.TestName());
}
[Test()]
public async Task GetChargeCodeCustomPickerMembersAsyncTest()
{
var result = await CCOpportunityService.GetChargeCodePickerModelMembers(new List<string>
{
"CC 1"
}, CancellationToken.None);
await Verifier.Verify(result, VerifySettings)
.UseFileName(TestContext.CurrentContext.TestName());
}
[Test()]
public async Task ValidateOpportunityLevelsAsyncTest()
{
Assert.Inconclusive();
}
[Test()]
public async Task CreateAsyncTest()
{
Assert.Inconclusive();
}
[Test(), Ignore("Still working on this")]
public async Task UpdateAsyncTest()
{
var dbGuid = JazzEntityFrameworkFactory.DatabaseGuid;
var opportunities = JAZZ_Framework.ChargeCodeOpportunities;
var opportunity = opportunities.FirstOrDefault();
opportunity.Name += " Updated";
var result = await CCOpportunityService.UpdateAsync(dbGuid, opportunity, CancellationToken.None);
await Verifier.Verify(result, VerifySettings)
.DontScrubGuids()
.UseFileName(TestContext.CurrentContext.TestName());
}
[Test()]
public async Task GetByIdTest()
{
var opportunities = JAZZ_Framework.CCOpportunities;
var opportunity = opportunities.FirstOrDefault();
var result = await CCOpportunityService.GetById(opportunity.OpportunityGuid, CancellationToken.None);
result.Configuration = null;
await Verifier.Verify(result, VerifySettings)
.DontScrubGuids()
.UseFileName(TestContext.CurrentContext.TestName());
}
[Test()]
public async Task DeleteAsyncTest()
{
Assert.Inconclusive();
}
[Test()]
public async Task GetInitiativeInfoAsyncTest()
{
var opportunities = JAZZ_Framework.CCOpportunities;
var opportunity = opportunities.FirstOrDefault();
var result = await CCOpportunityService.GetInitiativeInfoAsync(opportunity.OpportunityGuid, CancellationToken.None);
await Verifier.Verify(result, VerifySettings)
.DontScrubGuids()
.UseFileName(TestContext.CurrentContext.TestName());
}
[Test()]
public async Task RefreshOpportunityTrackingDataTest()
{
Assert.Inconclusive();
}
[Test(), Ignore("Still working on this")]
public async Task ExportAsyncTest()
{
var opportunities = JAZZ_Framework.CCOpportunities;
var opportunity = opportunities.FirstOrDefault();
var export = await CCOpportunityService.ExportAsync(opportunity.OpportunityGuid, new string[] { "CC 1" }, "", CancellationToken.None);
var result = export.GetDataTables();
await Verifier.Verify(result, VerifySettings)
.DontScrubGuids()
.UseFileName(TestContext.CurrentContext.TestName());
}
[Test()]
public async Task UpdateGoalTotalUnitsOfServiceAsyncTest()
{
var opportunities = JAZZ_Framework.CCOpportunities;
var opportunity = opportunities.FirstOrDefault();
var result = await CCOpportunityService.UpdateGoalTotalUnitsOfServiceAsync(opportunity.OpportunityGuid, "|CC 1|", 100, CancellationToken.None);
//await Verifier.Verify(result, VerifySettings)
// .DontScrubGuids()
// .UseFileName(TestContext.CurrentContext.TestName());
}
[Test()]
public async Task UpdateGoalCostPerUnitAsyncTest()
{
Assert.Inconclusive();
}
}
}
@@ -0,0 +1,78 @@
using Microsoft.Data.SqlClient;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
using Strata.ContinuousImprovement.Biz.Shared;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using VerifyNUnit;
using VerifyTests;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.ChargeCode.Opportunities
{
[TestFixture, Category("Unit")]
public class OpportunityDetailLevelQueryTests
{
private VerifySettings _verifySettings;
[SetUp]
public void TestSetup()
{
_verifySettings = TestExtensions.TestSettings();
}
[TestCaseSource(nameof(OpportunityDetailLevelQueryTestCases))]
public async Task OpportunityDetailLevelQueryTest(OpportunityLevel opportunityLevel, QueryHelper encounterQueryHelper,
QueryHelper opportunityLevelQueryHelper)
{
var query = new OpportunityDetailLevelQuery(opportunityLevel, encounterQueryHelper, opportunityLevelQueryHelper);
var result = query.GetQuery();
await Verifier.Verify(result, _verifySettings)
.UseFileName(TestContext.CurrentContext.TestName());
}
public static IEnumerable<TestCaseData> OpportunityDetailLevelQueryTestCases()
{
var configuration = Mock.Of<Biz.Configuration.Configuration>();
configuration.DepartmentRollupGlobalId = "";
var sqlParameters = new SqlParameter[] {
new SqlParameter("@chargeCodeIds", new List<int> { 1, 2, 3 }),
new SqlParameter("@caseTypeIds", new List<int> { 1, 2, 3 }),
new SqlParameter("@startDate", new DateTime(2023,1,1)),
new SqlParameter("@endDate", new DateTime(2024,1,1)),
new SqlParameter("@isBaseline", true)
};
foreach (var parameterName in new[] { "@caseTypeIds", "@indicatorIds" })
{
sqlParameters[1].ParameterName = parameterName;
foreach (var isUsingSnowflake in new[] { true, false })
{
foreach (var oppLevel in Enum.GetValues<OpportunityLevel>())
{
switch (oppLevel)
{
case OpportunityLevel.ServiceLine:
foreach (var serviceLine in new[] { "Sg2 Service Line Crosswalk", "Service Line" })
{
yield return new TestCaseData(oppLevel,
new EncounterGroupQueryHelper(sqlParameters),
new OpportunityLevelQueryHelper(oppLevel, configuration, serviceLine, $"{(serviceLine.Replace(" ", ""))}Id"))
.SetName($"{{m}} {Enum.GetName<OpportunityLevel>(oppLevel)} {parameterName.Substring(1)} {serviceLine} {(isUsingSnowflake ? "Snowflake" : "")}");
}
break;
default:
yield return new TestCaseData(oppLevel,
new EncounterGroupQueryHelper(sqlParameters),
new OpportunityLevelQueryHelper(oppLevel, configuration, "", ""))
.SetName($"{{m}} {Enum.GetName<OpportunityLevel>(oppLevel)} {parameterName.Substring(1)} Default {(isUsingSnowflake ? "Snowflake" : "")}");
break;
}
}
}
}
}
}
}
@@ -0,0 +1,85 @@
using Microsoft.Data.SqlClient;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities;
using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake;
using Strata.ContinuousImprovement.Biz.Shared;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using Strata.ContinuousImprovement.Biz.Utilities;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using VerifyNUnit;
using VerifyTests;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.ChargeCode.Opportunities
{
[TestFixture, Category("Unit")]
public class OpportunityDetailQueryTests
{
private VerifySettings _verifySettings;
private Biz.Configuration.Configuration _configuration;
[SetUp]
public void TestSetup()
{
_configuration = Mock.Of<Biz.Configuration.Configuration>();
_verifySettings = TestExtensions.TestSettings();
}
[TestCaseSource(nameof(OpportunityDetailQueryTestCases))]
public async Task OpportunityDetailQueryTest(OpportunityLevel opportunityLevel, IEnumerable<SqlParameter> sqlParameters, QueryHelper encounterQueryHelper, QueryHelper opportunityLevelQueryHelper, bool isUsingSnowflake)
{
var query = new OpportunityDetailQuery(opportunityLevel, encounterQueryHelper, opportunityLevelQueryHelper, isUsingSnowflake);
var result = isUsingSnowflake
? query.GetQuery().ConvertSqlToSnowflakeParameterSyntax()
: query.GetQuery().InterpolateQueryArrayValues(ref sqlParameters);
await Verifier.Verify(result, _verifySettings)
.UseFileName(TestContext.CurrentContext.TestName());
}
public static IEnumerable<TestCaseData> OpportunityDetailQueryTestCases()
{
var configuration = Mock.Of<Biz.Configuration.Configuration>();
configuration.DepartmentRollupGlobalId = "";
var sqlParameters = new SqlParameter[] {
new SqlParameter("@chargeCodeIds", new List<int> { 1, 2, 3 }),
new SqlParameter("@caseTypeIds", new List<int> { 7, 8, 9 }),
new SqlParameter("@oppLevelIds", new List<int>{ 4, 5, 6 }),
new SqlParameter("@startDate", new DateTime(2023,1,1)),
new SqlParameter("@endDate", new DateTime(2024,1,1)),
new SqlParameter("@isBaseline", true)
};
foreach (var parameterName in new[] { "@caseTypeIds", "@indicatorIds" })
{
sqlParameters[1].ParameterName = parameterName;
foreach (var isUsingSnowflake in new[] { true, false })
{
foreach (var oppLevel in Enum.GetValues<OpportunityLevel>())
{
switch (oppLevel)
{
case OpportunityLevel.ServiceLine:
foreach (var serviceLine in new[] { "Sg2 Service Line Crosswalk", "Service Line" })
{
yield return new TestCaseData(oppLevel, sqlParameters,
new EncounterGroupQueryHelper(sqlParameters),
new OpportunityLevelQueryHelper(oppLevel, configuration, serviceLine, $"{(serviceLine.Replace(" ", ""))}Id"), isUsingSnowflake)
.SetName($"{{m}} {Enum.GetName<OpportunityLevel>(oppLevel)} {parameterName.Substring(1)} {serviceLine} {(isUsingSnowflake ? "Snowflake" : "")}");
}
break;
default:
yield return new TestCaseData(oppLevel, sqlParameters,
new EncounterGroupQueryHelper(sqlParameters),
new OpportunityLevelQueryHelper(oppLevel, configuration, "", ""), isUsingSnowflake)
.SetName($"{{m}} {Enum.GetName<OpportunityLevel>(oppLevel)} {parameterName.Substring(1)} Default {(isUsingSnowflake ? "Snowflake" : "")}");
break;
}
}
}
}
}
}
}
@@ -0,0 +1,124 @@
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.Configuration;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.FiscalMonths;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Configuration
{
[TestFixture, Category("Unit")]
public class ConfigurationServiceTest
{
private Mock<IConfigurationService> _configurationServiceMock;
private Mock<IAsyncDbContextFactory<JazzDbContext>> _dbContextFactoryMock;
private Mock<IJazzDbContext> _jazzDbContextMock;
private Mock<IFiscalMonthResolver> _fiscalMonthResolverMock;
[SetUp]
public void TestSetUp()
{
_configurationServiceMock = new Mock<IConfigurationService>();
_dbContextFactoryMock = new Mock<IAsyncDbContextFactory<JazzDbContext>>();
_jazzDbContextMock = new Mock<IJazzDbContext>();
_fiscalMonthResolverMock = new Mock<IFiscalMonthResolver>();
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestGetDefaultConfigurationMock(JazzEntityFrameworkFactory framework, CancellationToken cancellationToken)
{
// arrange
await framework.CreateDbContextAsync(CancellationToken.None);
var dfltConfiguration = framework.Configurations.OrderBy(c => c.DisplayOrder).First();
_configurationServiceMock
.Setup(vos => vos
.GetDefaultConfigurationAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(dfltConfiguration);
var logger = Mock.Of<ILogger<ConfigurationService>>();
var _configurationService = new ConfigurationService(framework, null, null, logger, _fiscalMonthResolverMock.Object);
try
{
// act
var taskResult = await _configurationService.GetDefaultConfigurationAsync(cancellationToken);
// assert
if (taskResult == null)
{
framework.JazzDbContext.Should().BeNull();
return;
}
taskResult.Should().BeOfType<Biz.Configuration.Configuration>();
taskResult.Name.Should().Be(dfltConfiguration.Name);
taskResult.DisplayOrder.Should().Be(dfltConfiguration.DisplayOrder);
}
catch (OperationCanceledException)
{
cancellationToken.Should().NotBe(CancellationToken.None);
}
catch (Exception)
{
// Intentionally left empty
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestGetDefaultConfiguration(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
try
{
// arrange
var logger = Mock.Of<ILogger<ConfigurationService>>();
await jazzEntityFrameworkFactory.CreateDbContextAsync(CancellationToken.None);
var configurationService = new ConfigurationService(jazzEntityFrameworkFactory, null, null, logger, _fiscalMonthResolverMock.Object);
// act
var taskResult = await configurationService.GetDefaultConfigurationAsync(cancellationToken);
// assert
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName))
{
taskResult.Should().BeNull();
}
else
{
taskResult.Should().NotBeNull()
.And.BeOfType<Biz.Configuration.Configuration>();
taskResult.QVIOpportunityBreakdownName.Should().NotBeNullOrEmpty();
taskResult.QVIOpportunityBreakdownNamePlural.Should().NotBeNullOrEmpty();
taskResult.QVIOpportunityBreakdownEnum.Should()
.Be((int)taskResult.QVIOpportunityBreakdown);
}
}
catch (AggregateException ae)
{
ae.Should().NotBeOfType<AggregateException>();
}
catch (InvalidOperationException ioe)
{
ioe.Should().NotBeOfType<InvalidOperationException>();
}
catch (OperationCanceledException)
{
cancellationToken.Should().NotBe(CancellationToken.None);
}
catch (Exception)
{
// Intentionally left blank
}
}
[TearDown]
public void TestTearDown()
{
// Method intentionally left empty.
}
}
}
@@ -0,0 +1,244 @@
using FluentAssertions;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.FiscalMonths;
using Strata.ContinuousImprovement.Biz.Generics;
using System;
using System.Collections.Generic;
using System.Linq;
using config = Strata.ContinuousImprovement.Biz.Configuration;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Configuration
{
[TestFixture, Category("Unit")]
public class ConfigurationTest
{
private IEnumerable<OpportunityTypes> excludedOpportTypes = new List<OpportunityTypes>()
{
OpportunityTypes.Blank,
OpportunityTypes.Network
};
private IEnumerable<OpportunityTypes> OpportTypes { get; set; }
[OneTimeSetUp]
public void OneTimeSetup()
{
OpportTypes = Enum.GetValues(typeof(OpportunityTypes))
.Cast<OpportunityTypes>()
.Where(x => !excludedOpportTypes.Contains(x));
}
[TestCaseSource(nameof(ConfigTestCases))]
public void TestConfiguration(string rollup, string ids, int breakdown,
IEnumerable<int> configuredIds, string name, string plural)
{
var config = new config.Configuration
{
DepartmentRollupGlobalId = rollup,
EntityIdsCSV = string.IsNullOrEmpty(rollup) ? "" : ids,
CustomEntityIdsCSV = string.IsNullOrEmpty(rollup) ? ids : "",
QVIOpportunityBreakdown = (Biz.Configuration.Configuration.QVIOpportunityBreakdowns)breakdown
};
config.ConfiguredEntityIds.Should().AllBeEquivalentTo(configuredIds);
config.QVIOpportunityBreakdownEnum.Should().Be(breakdown);
config.QVIOpportunityBreakdownName.Should().Be(name);
config.QVIOpportunityBreakdownNamePlural.Should().Be(plural);
config.ServiceLineDimensionGuid.Should().BeEmpty();
}
[TestCaseSource(nameof(StartEndDateTestCases))]
public void TestGetBaselineStartDate(DateTime expectedStartDate, DateTime endDate)
{
var configuration = GetConfigurationByDate(endDate);
var opportunityTypeIds = Enum.GetValues<OpportunityTypes>().Cast<byte>().ToList();
foreach (var opType in OpportTypes.Where(t => opportunityTypeIds.Contains((byte)t)))
{
var actualStartDate = configuration.GetBaselineStartDate(opType);
actualStartDate.Should().Be(expectedStartDate);
}
}
[TestCaseSource(nameof(StartEndDateTestCases))]
public void TestGetLastFiscalYearStartDate(DateTime expectedStartDate, DateTime endDate)
{
var configuration = GetConfigurationByDate(endDate, true);
var actualStartDate = configuration.GetLastFiscalYearStartDate();
expectedStartDate = expectedStartDate.AddYears(-1);
actualStartDate.Should().Be(expectedStartDate);
}
[TestCaseSource(nameof(StartEndDateTestCases))]
public void TestGetFiscalYearStartDate(DateTime expectedStartDate, DateTime endDate)
{
var configuration = GetConfigurationByDate(endDate, true);
var actualStartDate = configuration.GetFiscalYearStartDate();
actualStartDate.Should().Be(expectedStartDate);
}
[TestCaseSource(nameof(TrackingCurrentMonthEndDateTestCases))]
public void TestTrackingCurrentMonthEndDate(
OpportunityTypes opportunityType,
DateTime trackingCurrentMonth,
DateTime expectedEndDate)
{
var configuration = GetConfigurationWithTrackingMonth(trackingCurrentMonth, opportunityType);
var actualEndDate = configuration.TrackingCurrentMonthEndDate(opportunityType);
actualEndDate.Should().Be(expectedEndDate);
actualEndDate.Hour.Should().Be(0);
actualEndDate.Minute.Should().Be(0);
actualEndDate.Second.Should().Be(0);
}
[TestCaseSource(nameof(TrackingCurrentMonthEndDateTestCases))]
public void TestGetTrackingCurrentMonthEndDate(
OpportunityTypes opportunityType,
DateTime trackingCurrentMonth,
DateTime expectedEndDate)
{
var configuration = GetConfigurationWithTrackingMonth(trackingCurrentMonth, opportunityType);
var actualEndDate = configuration.GetTrackingCurrentMonthEndDate(opportunityType);
actualEndDate.Should().Be(expectedEndDate.AddDays(-1));
}
#region Configuration Test Cases
private static IEnumerable<TestCaseData> ConfigTestCases()
{
yield return new TestCaseData("Rollup", "1,2,3,4", 4,
new[] { 1, 2, 3, 4 }, "Breakdown", "Breakdowns")
.SetName("Default With Rollup");
yield return new TestCaseData("", "1,2,3,4", 4,
new[] { 1, 2, 3, 4 }, "Breakdown", "Breakdowns")
.SetName("Default w/o Rollup");
yield return new TestCaseData("Rollup", "1,2,3,4",
(int)config.Configuration.QVIOpportunityBreakdowns.Entity,
new[] { 1, 2, 3, 4 }, "Entity", "Entities")
.SetName("Entity With Rollup");
yield return new TestCaseData("", "1,2,3,4",
(int)config.Configuration.QVIOpportunityBreakdowns.Entity,
new[] { 1, 2, 3, 4 }, "Entity", "Entities")
.SetName("Entity w/o Rollup");
yield return new TestCaseData("Rollup", "1,2,3,4",
(int)config.Configuration.QVIOpportunityBreakdowns.ServiceLine,
new[] { 1, 2, 3, 4 }, "Service Line", "Service Lines")
.SetName("Service Line With Rollup");
yield return new TestCaseData("", "1,2,3,4",
(int)config.Configuration.QVIOpportunityBreakdowns.ServiceLine,
new[] { 1, 2, 3, 4 }, "Service Line", "Service Lines")
.SetName("Service Line w/o Rollup");
yield return new TestCaseData("Rollup", "1,2,3,4",
(int)config.Configuration.QVIOpportunityBreakdowns.PhysicianSpecialty,
new[] { 1, 2, 3, 4 }, "Physician Specialty", "Physician Specialties")
.SetName("Physician Specialty With Rollup");
yield return new TestCaseData("", "1,2,3,4",
(int)config.Configuration.QVIOpportunityBreakdowns.PhysicianSpecialty,
new[] { 1, 2, 3, 4 }, "Physician Specialty", "Physician Specialties")
.SetName("Physician Specialty w/o Rollup");
}
private static IEnumerable<TestCaseData> StartEndDateTestCases()
{
yield return new TestCaseData(new DateTime(2023, 3, 1), new DateTime(2024, 2, 29));
yield return new TestCaseData(new DateTime(2024, 3, 1), new DateTime(2025, 2, 28));
}
private static IEnumerable<TestCaseData> TrackingCurrentMonthEndDateTestCases()
{
yield return new TestCaseData(
OpportunityTypes.ChargeCode,
new DateTime(2024, 3, 15),
new DateTime(2024, 4, 1))
.SetName("ChargeCode - March 2024");
yield return new TestCaseData(
OpportunityTypes.QualityVariation,
new DateTime(2024, 6, 20),
new DateTime(2024, 7, 1))
.SetName("QualityVariation - June 2024");
yield return new TestCaseData(
OpportunityTypes.Variation,
new DateTime(2024, 12, 5),
new DateTime(2025, 1, 1))
.SetName("Variation - December 2024");
yield return new TestCaseData(
OpportunityTypes.OrgDefined,
new DateTime(2024, 2, 10),
new DateTime(2024, 3, 1))
.SetName("OrgDefined - February 2024");
yield return new TestCaseData(
OpportunityTypes.Exploration,
new DateTime(2024, 1, 31),
new DateTime(2024, 2, 1))
.SetName("Exploration - January 2024");
yield return new TestCaseData(
OpportunityTypes.ChargeCode,
new DateTime(2024, 2, 29),
new DateTime(2024, 3, 1))
.SetName("ChargeCode - Leap Year February");
}
#endregion
#region Utils
private static config.Configuration GetConfigurationByDate(DateTime date, bool isFiscalMonthResolver = false)
{
var configuration = new config.Configuration
{
FiscalYearID = (short)date.Year,
OrgDefinedEndDate = date,
VariationEndDate = date,
QualityVariationEndDate = date
};
if (isFiscalMonthResolver)
{
var lastFiscalMonth = new FiscalMonth
{
FiscalMonthId = (byte)date.Month
};
var fmr = Mock.Of<IFiscalMonthResolver>();
Mock.Get(fmr)
.Setup(s => s.LastFiscalMonth())
.Returns(() => lastFiscalMonth);
configuration.FiscalMonthResolver = fmr;
}
return configuration;
}
private static config.Configuration GetConfigurationWithTrackingMonth(
DateTime trackingCurrentMonth,
OpportunityTypes opportunityType)
{
var configuration = new config.Configuration();
switch (opportunityType)
{
case OpportunityTypes.ChargeCode:
configuration.OrgDefinedTrackingCurrentMonth = trackingCurrentMonth;
break;
case OpportunityTypes.QualityVariation:
configuration.QualityVariationTrackingCurrentMonth = trackingCurrentMonth;
break;
default:
configuration.VariationTrackingCurrentMonth = trackingCurrentMonth;
break;
}
return configuration;
}
#endregion
}
}
@@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore;
using Moq;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.DbContexts
{
[ExcludeFromCodeCoverage]
public static class DbSetExtensions
{
public static DbSet<T> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>((s) => sourceList.Add(s));
return dbSet.Object;
}
}
}
@@ -0,0 +1,247 @@
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities;
using Strata.ContinuousImprovement.Biz.Generics;
using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.DbContexts
{
[TestFixture, Category("Unit")]
public class JazzDbContextTests
{
private JazzDbContext _context;
private DbContextOptions<JazzDbContext> _options;
[OneTimeSetUp]
public void OneTimeSetUp()
{
_options = new DbContextOptionsBuilder<JazzDbContext>()
.UseInMemoryDatabase($"TestDb_{Guid.NewGuid()}")
.ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.Options;
}
[SetUp]
public void SetUp()
{
_context = new JazzDbContext(_options);
}
[TearDown]
public void TearDown()
{
_context?.Dispose();
}
[Test]
public void ConfigureConventions_ShouldSetDecimalPrecision_ForAllDecimalProperties()
{
// Act
var model = _context.Model;
// Assert - Get all decimal properties from all entities
var decimalProperties = model.GetEntityTypes()
.SelectMany(e => e.GetProperties())
.Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?))
.ToList();
// Verify that we have decimal properties to test
decimalProperties.Should().NotBeEmpty("There should be decimal properties in the model to test");
// Check that all decimal properties have the expected precision and scale
var foregroundColor = Console.ForegroundColor;
foreach (var property in decimalProperties)
{
var className = ((IEntityType)property.DeclaringType).ClrType.Name;
var propertyName = property.Name;
var precision = property.GetPrecision();
var scale = property.GetScale();
if (precision == null || scale == null)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"Property {className}.{propertyName} does not have precision/scale configured.");
}
else if (className == "QVIOpportunity" && propertyName == "QVIRate")
{
precision.Should().Be(19, $"Property {className}.{propertyName} should have precision 19");
scale.Should().Be(4, $"Property {className}.{propertyName} should have scale 4");
}
else if (propertyName.StartsWith("Month"))
{
precision.Should().Be(15, $"Property {className}.{propertyName} should have precision 15");
scale.Should().Be(4, $"Property {className}.{propertyName} should have scale 4");
}
else if (propertyName.StartsWith("QVI"))
{
precision.Should().Be(29, $"Property {className}.{propertyName} should have precision 29");
scale.Should().Be(11, $"Property {className}.{propertyName} should have scale 11");
}
}
Console.ForegroundColor = foregroundColor;
}
[Test]
public void ConfigureConventions_ShouldNotAffectNonDecimalProperties()
{
// Act
var model = _context.Model;
// Assert - Check that non-decimal properties are not affected
var nonDecimalProperties = model.GetEntityTypes()
.SelectMany(e => e.GetProperties())
.Where(p => p.ClrType != typeof(decimal) && p.ClrType != typeof(decimal?))
.ToList();
nonDecimalProperties.Should().NotBeEmpty("There should be non-decimal properties to verify they're not affected");
foreach (var property in nonDecimalProperties)
{
var precision = property.GetPrecision();
var scale = property.GetScale();
if (property.ClrType == typeof(string))
{
precision.Should().BeNull($"String property {((IEntityType)property.DeclaringType).ClrType.Name}.{property.Name} should not have precision");
scale.Should().BeNull($"String property {((IEntityType)property.DeclaringType).ClrType.Name}.{property.Name} should not have scale");
}
}
}
[Test]
public async Task DatabaseCreation_ShouldSucceed_WithDecimalPrecisionConfiguration()
{
// Act & Assert
var canConnect = await _context.Database.CanConnectAsync();
canConnect.Should().BeTrue("Database should be accessible with decimal precision configuration");
await _context.Database.EnsureCreatedAsync();
var created = await _context.Database.CanConnectAsync();
created.Should().BeTrue("Database should be created successfully with decimal precision configuration");
}
[Test]
public void ModelValidation_ShouldPass_WithDecimalPrecisionConfiguration()
{
// Arrange & Act
Action validateModel = () =>
{
// Accessing the model is enough to trigger validation in EF Core.
// No explicit FinalizeModel method exists on IModel.
var _ = _context.Model.GetEntityTypes().ToList();
};
// Assert
validateModel.Should().NotThrow("Model should validate successfully with decimal precision configuration");
}
public static IEnumerable<TestCaseData> OpportunityTestCases()
{
yield return new TestCaseData(new QVIOpportunity
{
OpportunityGuid = Guid.NewGuid(),
ConfigurationGuid = Guid.NewGuid(),
IdentifiedSavings = (double)98765.432102m,
QVIRate = (decimal)98765.432102d,
OpportunityKey = "TEST-004"
}, (double)98765.4321m, (decimal)98765.4321d);
}
private void AddOpportunityToContext(Opportunity opportunity)
{
switch (opportunity)
{
case QVIOpportunity qviOpportunity:
_context.QualityVariationItemOpportunities.Add(qviOpportunity);
break;
default:
throw new ArgumentException($"Unsupported opportunity type: {opportunity.GetType().Name}");
}
}
private async Task<object> GetSavedOpportunityAsync(Opportunity opportunity)
{
return opportunity switch
{
QVIOpportunity qviOpportunity => await _context.QualityVariationItemOpportunities
.FirstOrDefaultAsync(o => o.OpportunityGuid == qviOpportunity.OpportunityGuid),
_ => throw new ArgumentException($"Unsupported opportunity type: {opportunity.GetType().Name}")
};
}
[Test, TestCaseSource(nameof(OpportunityTestCases))]
public async Task DecimalProperty_ShouldHandlePrecisionCorrectly_InDatabase(Opportunity testOpportunity, double expectedIdentifiedSavings, decimal? expectedQVIRate = null)
{
// This test verifies that the precision works in practice with the InMemory provider
// Note: InMemory provider doesn't enforce precision/scale, but this tests the configuration doesn't break anything
// Arrange
await _context.Database.EnsureCreatedAsync();
// Act
if (testOpportunity == null)
{
throw new ArgumentNullException(nameof(testOpportunity), "Test opportunity cannot be null");
}
AddOpportunityToContext(testOpportunity);
// Assert
Func<Task> saveAction = async () => await _context.SaveChangesAsync();
await saveAction.Should().NotThrowAsync("Saving with configured decimal precision should work");
var savedOpportunity = await GetSavedOpportunityAsync(testOpportunity);
savedOpportunity.Should().NotBeNull();
const double ToleranceForDecimalComparison = 19d;
if (savedOpportunity is FFOpportunityDataModel ffOppTest)
{
ffOppTest.IdentifiedSavings.Should().BeApproximately(expectedIdentifiedSavings, ToleranceForDecimalComparison);
}
else
{
((Opportunity)savedOpportunity).IdentifiedSavings.Should().BeApproximately(expectedIdentifiedSavings, ToleranceForDecimalComparison);
if (savedOpportunity is QVIOpportunity qviOppTest && expectedQVIRate.HasValue)
{
qviOppTest.QVIRate.Should().BeApproximately(expectedQVIRate.Value, (decimal)ToleranceForDecimalComparison);
}
}
}
[Test]
public void BaseMethod_ShouldBeCalled_InConfigureConventions()
{
// This test ensures that base.ConfigureConventions() is still called
// We can't directly test this, but we can verify that EF Core's base conventions are applied
var model = _context.Model;
// Verify that base EF Core conventions are still working
var stringProperties = model.GetEntityTypes()
.SelectMany(e => e.GetProperties())
.Where(p => p.ClrType == typeof(string))
.ToList();
stringProperties.Should().NotBeEmpty("Should have string properties to verify base conventions work");
// EF Core base conventions should still work for string properties
foreach (var property in stringProperties.Take(5)) // Just check a few
{
// Base conventions should still be applied (this varies by property, so we just ensure no exceptions)
Action getAnnotations = () => { var _ = property.GetAnnotations(); };
getAnnotations.Should().NotThrow($"Base conventions should work for {((IEntityType)property.DeclaringType).ClrType.Name}.{property.Name}");
}
}
}
}
@@ -0,0 +1,185 @@
using FluentAssertions;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.Dtos;
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities;
using Strata.ContinuousImprovement.Biz.Mappers;
using Strata.ContinuousImprovement.Biz.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Encounter
{
[TestFixture, Category("Unit")]
public class EncounterOpportunityMetricTest
{
private ENOpportunityDto opportunity;
private ENOpportunityDataModelWithFilters model;
[SetUp]
public void TestSetup()
{
opportunity = new ENOpportunityDto
{
ConfigurationGuid = Guid.NewGuid(),
Name = "Test Encounter Opportunity",
EncounterGroup = 0,
OpportunityLevel = 1,
EncounterGroups = new List<string>
{
"CaseType|CaseType|1"
},
OpportunityLevels = new List<string>()
{
"ENT|ENT|1"
},
IsBaseline = true
};
model = opportunity.MapToDataModel(null);
model.OpportunityGuid = Guid.NewGuid();
model.AuthorGuid = Guid.NewGuid();
model.AuthorName = "Someones Name";
model.DateCreated = DateTime.Today;
}
[Test]
public void TestENOpportunityDataModel()
{
var data = opportunity.MapToDataModel(null);
data.ConfigurationGuid.Should().NotBe(Guid.Empty)
.And.Be(opportunity.ConfigurationGuid);
data.EncounterGroup.Should().Be(opportunity.EncounterGroup);
data.OpportunityLevel.Should().Be(Enum.GetName(typeof(OpportunityLevel), opportunity.OpportunityLevel));
data.EncounterOpportunityLevel.Should().Be((OpportunityLevel)opportunity.OpportunityLevel);
data.CaseTypes.Should().NotBeEmpty()
.And.Contain(ct => opportunity.EncounterGroups.Select(x => x).Contains(ct.Id));
data.IsSeasonal.Should().Be(opportunity.IsSeasonal);
data.IsBaseline.Should().Be(opportunity.IsBaseline);
}
[Test]
public void TestEncounterOpportunityMetric()
{
var metric = model.MapFromDataModel();
metric.OpportunityGuid.Should().Be(model.OpportunityGuid);
metric.ConfigurationGuid.Should().Be(model.ConfigurationGuid);
metric.AuthorName.Should().Be(model.AuthorName);
metric.AuthorGuid.Should().Be(model.AuthorGuid);
metric.Name.Should().Be(model.Name);
metric.EncounterOpportunityLevel.Should().Be(model.EncounterOpportunityLevel);
metric.EncounterGroup.Should().Be(model.EncounterGroup);
metric.CaseTypes.Should().NotBeEmpty()
.And.Contain(ct => opportunity.EncounterGroups.Select(x => x).Contains(ct.Id));
var filter = opportunity.EncounterGroups.First().MapToFilterHelper();
metric.CaseTypes.First().GetHashCode().Equals(filter.GetHashCode());
metric.PatientPopulations.Should().BeEmpty();
metric.Entities.Should().NotBeEmpty()
.And.Contain(ct => opportunity.OpportunityLevels.Select(x => x).Contains(ct.Id));
metric.ServiceLines.Should().BeEmpty();
metric.Specialties.Should().BeEmpty();
metric.Providers.Should().BeEmpty();
metric.IsBaseline.Should().Be(opportunity.IsBaseline);
metric.ConfigurationGuid.Should().NotBe(Guid.Empty);
metric.IsSeasonal.Should().Be(opportunity.IsSeasonal);
metric.IsBaseline.Should().Be(opportunity.IsBaseline);
}
[TestCaseSource(nameof(GetEncounterOpportunityMetricTests))]
public void TestEncounterOpportunityMetric(ENOpportunityDto enOpportunity)
{
var dataModel = opportunity.MapToDataModel(null);
var metric = dataModel.MapFromDataModel();
metric.OpportunityGuid.Should().Be(dataModel.OpportunityGuid);
metric.ConfigurationGuid.Should().Be(dataModel.ConfigurationGuid);
metric.AuthorName.Should().Be(dataModel.AuthorName);
metric.AuthorGuid.Should().Be(dataModel.AuthorGuid);
metric.Name.Should().Be(dataModel.Name);
metric.EncounterOpportunityLevel.Should().Be(dataModel.EncounterOpportunityLevel);
metric.EncounterGroup.Should().Be(dataModel.EncounterGroup);
switch ((EncounterGroup)metric.EncounterGroup)
{
case EncounterGroup.CaseType:
metric.CaseTypes.Should().NotBeEmpty()
.And.Contain(ct => opportunity.EncounterGroups.Select(x => x).Contains(ct.Id));
var filter = opportunity.EncounterGroups.First().MapToFilterHelper();
metric.CaseTypes.First().GetHashCode().Equals(filter.GetHashCode());
break;
case EncounterGroup.PatientPopulation:
metric.PatientPopulations.Should().NotBeEmpty()
.And.Contain(ct => opportunity.EncounterGroups.Select(x => x).Contains(ct.Id));
var filter1 = opportunity.EncounterGroups.First().MapToFilterHelper();
metric.PatientPopulations.First().GetHashCode().Equals(filter1.GetHashCode());
break;
}
metric.PatientPopulations.Should().BeEmpty();
switch (metric.EncounterOpportunityLevel)
{
case OpportunityLevel.Organization:
metric.Entities.Should().BeEmpty();
metric.ServiceLines.Should().BeEmpty();
metric.Specialties.Should().BeEmpty();
metric.Providers.Should().BeEmpty();
break;
case OpportunityLevel.Entity:
metric.Entities.Should().NotBeEmpty()
.And.Contain(ct => opportunity.OpportunityLevels.Select(x => x).Contains(ct.Id));
metric.ServiceLines.Should().BeEmpty();
metric.Specialties.Should().BeEmpty();
metric.Providers.Should().BeEmpty();
break;
case OpportunityLevel.ServiceLine:
metric.ServiceLines.Should().NotBeEmpty()
.And.Contain(ct => opportunity.OpportunityLevels.Select(x => x).Contains(ct.Id));
metric.Entities.Should().BeEmpty();
metric.Specialties.Should().BeEmpty();
metric.Providers.Should().BeEmpty();
break;
case OpportunityLevel.Specialty:
metric.Specialties.Should().NotBeEmpty()
.And.Contain(ct => opportunity.OpportunityLevels.Select(x => x).Contains(ct.Id));
metric.Entities.Should().BeEmpty();
metric.ServiceLines.Should().BeEmpty();
metric.Providers.Should().BeEmpty();
break;
case OpportunityLevel.Provider:
metric.Providers.Should().NotBeEmpty()
.And.Contain(ct => opportunity.OpportunityLevels.Select(x => x).Contains(ct.Id));
metric.Entities.Should().BeEmpty();
metric.ServiceLines.Should().BeEmpty();
metric.Specialties.Should().BeEmpty();
break;
}
metric.IsBaseline.Should().Be(opportunity.IsBaseline);
metric.ConfigurationGuid.Should().NotBe(Guid.Empty);
metric.IsSeasonal.Should().Be(opportunity.IsSeasonal);
metric.IsBaseline.Should().Be(opportunity.IsBaseline);
}
public static IEnumerable<TestCaseData> GetEncounterOpportunityMetricTests()
{
foreach (EncounterGroup encounterGroup in Enum.GetValues(typeof(EncounterGroup)))
{
if (encounterGroup == EncounterGroup.Both) continue;
foreach (OpportunityLevel oppLevel in Enum.GetValues(typeof(OpportunityLevel)))
{
yield return new TestCaseData(new ENOpportunityDto
{
ConfigurationGuid = Guid.NewGuid(),
Name = "Test Encounter Opportunity",
EncounterGroup = (int)encounterGroup,
OpportunityLevel = (int)oppLevel,
EncounterGroups = new List<string>
{
"CaseType|CaseType|1"
},
OpportunityLevels = new List<string>()
{
"ENT|ENT|1"
},
IsBaseline = true
}).SetName($"TestEncounterOpportunityMetric {encounterGroup} {oppLevel}");
}
}
}
}
}
@@ -0,0 +1,813 @@
using FluentAssertions;
using Hangfire;
using Hangfire.Common;
using Hangfire.States;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Moq;
using NUnit.Framework;
using Strata.Authorization;
using Strata.ContinuousImprovement.Biz.Configuration;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.Encounter;
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities;
using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters;
using Strata.ContinuousImprovement.Biz.Filter;
using Strata.ContinuousImprovement.Biz.FiscalMonths;
using Strata.ContinuousImprovement.Biz.Generics;
using Strata.ContinuousImprovement.Biz.Initiatives;
using Strata.ContinuousImprovement.Biz.Notification;
using Strata.ContinuousImprovement.Biz.Pagination;
using Strata.ContinuousImprovement.Biz.Shared;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using Strata.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.Hangfire.Jazz.Client;
using Strata.Hangfire.Jazz.Client.Models;
using Strata.Id.Client;
using Strata.Schema.Client;
using Strata.Schema.Client.Dtos;
using Strata.Schema.Client.Dtos.Info;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Encounter.Opportunities
{
[TestFixture, Category("Unit")]
public class ENOpportunityServiceTests
{
private IENOpportunityService _encounterOpportunityService;
private Mock<IAsyncDbContextFactory<JazzDbContext>> _dbContextFactoryMock;
private Mock<IJazzDbContext> _jazzDbContextMock;
private Mock<ISchemaServiceClient> _schemaServiceMock;
private Mock<IJazzHangfireServiceClient> _hangfireServiceMock;
private Mock<IConfigurationService> _configurationMock;
private Mock<IFiscalMonthResolver> _fiscalMonthResolverMock;
private Mock<IBackgroundJobClient> _backgroundJobClient;
private Mock<IIdServiceClient> _idServiceClient;
private Mock<IAuthorizationServiceClient> _authorizationServiceClient;
private Mock<IHubContext<NotificationHub, INotificationHub>> _notificationHubContext;
private IInitiativeService _initiativeService;
private IUpdateFactOpportunitySavingsService _updateFactOpportunitySavingsService;
private static readonly ResourceFileExtractor _extractor = new ResourceFileExtractor(".Resource.");
private List<Biz.Configuration.Configuration> _configurations;
internal static List<Guid> ConfigGuids = new() { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
internal static List<string> ConfigNames = new() { "FY2021", "FY2022", "FY2023", "FY2024" };
private ScoreDimensionInfoDto _dimension;
private List<ScoreHierarchyDto> _hierarchies;
private List<HierarchyNodeDto> _hierarchyNodes;
private List<FilterHelper> _filterHelpers;
private JobEnqueuedResponse _jobResponse;
[SetUp]
public void TestSetUp()
{
_dbContextFactoryMock = new Mock<IAsyncDbContextFactory<JazzDbContext>>();
_jazzDbContextMock = new Mock<IJazzDbContext>();
_schemaServiceMock = new Mock<ISchemaServiceClient>();
_hangfireServiceMock = new Mock<IJazzHangfireServiceClient>();
_configurationMock = new Mock<IConfigurationService>();
_fiscalMonthResolverMock = new Mock<IFiscalMonthResolver>();
_updateFactOpportunitySavingsService = Mock.Of<IUpdateFactOpportunitySavingsService>();
_initiativeService = Mock.Of<IInitiativeService>();
_authorizationServiceClient = new Mock<IAuthorizationServiceClient>();
_notificationHubContext = new Mock<IHubContext<NotificationHub, INotificationHub>>();
_notificationHubContext.Setup(s => s.Clients)
.Returns(new Mock<IHubClients<INotificationHub>>().Object);
Mock.Get(_updateFactOpportunitySavingsService)
.Setup(s => s.UpdateFactOpportunitySavings(It.IsAny<CancellationToken>()));
Mock.Get(_initiativeService)
.Setup(s => s.DeleteAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(true));
_backgroundJobClient = new Mock<IBackgroundJobClient>();
_idServiceClient = new Mock<IIdServiceClient>();
_encounterOpportunityService = new ENOpportunityService(_dbContextFactoryMock.Object, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
_configurations = new List<Biz.Configuration.Configuration>
{
new()
{
ConfigurationGuid = ConfigGuids[0],
Name = ConfigNames.First(),
DepartmentRollupGlobalId = string.Empty,
ServiceLineDimensionGuid = Guid.NewGuid()
},
new()
{
ConfigurationGuid = ConfigGuids[1],
Name = ConfigNames[1],
DepartmentRollupGlobalId = "Department",
ServiceLineDimensionGuid = Guid.NewGuid()
},
new()
{
ConfigurationGuid = ConfigGuids[2],
Name = ConfigNames[2],
DepartmentRollupGlobalId = string.Empty,
ServiceLineDimensionGuid = Guid.NewGuid()
},
new()
{
ConfigurationGuid = ConfigGuids[3],
Name = ConfigNames.Last(),
DepartmentRollupGlobalId = string.Empty,
ServiceLineDimensionGuid = Guid.NewGuid()
}
};
_dimension = new ScoreDimensionInfoDto
{
FriendlyName = "test",
DimensionGuid = Guid.NewGuid(),
DefaultHierarchyGuid = Guid.NewGuid()
};
_hierarchies = new List<ScoreHierarchyDto>
{
new ScoreHierarchyDto
{
FriendlyName = "test",
HierarchyGuid = _dimension.DefaultHierarchyGuid
}
};
_hierarchyNodes = new List<HierarchyNodeDto>
{
new HierarchyNodeDto
{
Name = "Case Type",
Path = "CaseType|CaseType|1"
}
};
_jobResponse = new JobEnqueuedResponse
{
JobId = Guid.NewGuid()
};
_filterHelpers = new List<FilterHelper>
{
new FilterHelper
{
Id = "CaseType|CaseType|1"
}
};
_hangfireServiceMock.Setup(moq =>
moq.EnqueueJobAsync(It.IsAny<EnqueueJobDto>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_jobResponse));
_configurationMock.Setup(moq => moq.GetConfigurationAsync(It.Is<Guid>(c => c == ConfigGuids[0]), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_configurations[0]));
_configurationMock.Setup(moq => moq.GetConfigurationAsync(It.Is<Guid>(c => c == ConfigGuids[1]), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_configurations[1]));
_configurationMock.Setup(moq => moq.GetConfigurationAsync(It.Is<Guid>(c => c == ConfigGuids[2]), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_configurations[2]));
_configurationMock.Setup(moq => moq.GetConfigurationAsync(It.Is<Guid>(c => c == _configurations.Last().ConfigurationGuid), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_configurations.Last()));
_schemaServiceMock.Setup(moq => moq.GetDimensionByGlobalIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_dimension));
_schemaServiceMock.Setup(moq => moq.GetDimensionByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_dimension));
_schemaServiceMock.Setup(moq => moq.GetScoreHierarchiesByGlobalIdAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_hierarchies.AsEnumerable()));
_schemaServiceMock.Setup(moq => moq.GetHierarchyNodesByHierarchyGuidAsync(It.IsAny<Guid>(),
It.IsAny<HierarchyNodeParametersDto>(), It.IsAny<List<ExtraFilterDto>>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_hierarchyNodes.AsEnumerable()));
_schemaServiceMock.Setup(moq => moq.GetHierarchyNodesFromHierarchyPathsAsync(It.IsAny<Guid>(),
It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(_hierarchyNodes.AsEnumerable()));
_backgroundJobClient.Setup(moq => moq.Create(It.IsAny<Job>(), It.IsAny<IState>()))
.Returns(Guid.NewGuid().ToString());
}
[Test]
public async Task TestFirstPage_Sorted_Asc()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var encounterOpportunityService = new ENOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
var filters = new ENFilters()
{
Cases = 0,
OpportunityLevels = new List<string>(),
EncounterGroups = EncounterGroup.Both,
Seasonality = SeasonalityType.WithAndWithout,
IdentifiedSavings = 0,
ViewVisible = true,
ExcludeInitiatives = false,
OpportunitySearch = "",
ConfigurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid
};
var pagingOptions = new PagingOptions("IdentifiedSavings", SortOrder.Asc);
// act
var taskResult = await encounterOpportunityService
.GetPagedAsync(pagingOptions, filters, CancellationToken.None);
// assert
taskResult.Data.Should().NotBeNullOrEmpty()
.And.AllBeOfType<ENOpportunity>()
.And.HaveCountGreaterThan(1);
taskResult.Data.First().IdentifiedSavings
.Should().BeLessThan(taskResult.Data.Last().IdentifiedSavings);
}
[Test]
public async Task TestFirstPage_Sorted_Desc()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var encounterOpportunityService = new ENOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
var filters = new ENFilters()
{
Cases = 0,
OpportunityLevels = new List<string>(),
EncounterGroups = EncounterGroup.Both,
Seasonality = SeasonalityType.WithAndWithout,
IdentifiedSavings = 0,
ViewVisible = true,
ExcludeInitiatives = false,
OpportunitySearch = "",
ConfigurationGuid = JazzEntityFrameworkFactory.CurrentConfigurationGuid
};
var pagingOptions = new PagingOptions("IdentifiedSavings", SortOrder.Desc);
// act
var taskResult = await encounterOpportunityService
.GetPagedAsync(pagingOptions, filters, CancellationToken.None);
// assert
taskResult.Data.Should().NotBeNullOrEmpty()
.And.AllBeOfType<ENOpportunity>()
.And.HaveCountGreaterThan(1);
taskResult.Data.First().IdentifiedSavings
.Should().BeGreaterThan(taskResult.Data.Last().IdentifiedSavings);
}
[TestCaseSource(nameof(GetFilterData))]
public void TestValidateFilters(ENFilters filters)
{
if (filters == null) return;
if (filters.OpportunityLevels.Any())
filters.OpportunityLevels.Should()
.AllBeOfType<string>()
.And.NotBeNullOrEmpty();
}
[Test]
public async Task TestGetExcelWorkbook()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var opp = framework.ENOpportunities.First();
var filters = new ENFilters()
{
ConfigurationGuid = framework.Configurations.First().ConfigurationGuid,
OpportunityLevels = new[] { opp.OpportunityLevel },
};
var service = new ENOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var wb = await service.ExportAsync(new SortOptions(), filters, CancellationToken.None);
#pragma warning disable S125 // Sections of code should not be commented out
//wb.SaveAs(System.IO.Path.Combine(@"C:\Git\continuousimprovement\tests\Strata.ContinuousImprovement.Biz.Test.Unit\Resource", "ExcelFilter.xlsx"));
#pragma warning restore S125 // Sections of code should not be commented out
// assert
wb.Worksheets.Should().HaveCount(1);
var ws = wb.Worksheet(1);
ws.Should().NotBeNull();
var expected = @"Examples.EncounterOpportunity.ExcelFilter.xlsx";
using (var expectedStream = _extractor.ReadFileFromResourceToStream(expected))
using (var actualStream = new MemoryStream())
{
wb.SaveAs(actualStream);
actualStream.Compare(expectedStream, out var message).Should().BeTrue(message);
}
}
[Test]
public async Task TestGetExcelWorkbookNullFilter()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var service = new ENOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var wb = await service.ExportAsync(null, null, CancellationToken.None);
#pragma warning disable S125 // Sections of code should not be commented out
//wb.SaveAs(System.IO.Path.Combine(@"C:\Git\continuousimprovement\tests\Strata.ContinuousImprovement.Biz.Test.Unit\Resource", "ExcelNullFilter.xlsx"));
#pragma warning restore S125 // Sections of code should not be commented out
// assert
wb.Worksheets.Should().HaveCount(1);
var ws = wb.Worksheet(1);
ws.Should().NotBeNull();
var expected = @"Examples.EncounterOpportunity.ExcelNullFilter.xlsx";
using (var expectedStream = _extractor.ReadFileFromResourceToStream(expected))
using (var actualStream = new MemoryStream())
{
wb.SaveAs(actualStream);
actualStream.Compare(expectedStream, out var message).Should().BeTrue(message);
}
}
[Test]
public async Task TestCreateAsyncCaseTypesOrganization()
{
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var opportunity = new ENOpportunityDataModelWithFilters
{
EncounterGroup = 0,
CaseTypes = new List<FilterHelper>
{
new FilterHelper
{
Id = "CaseType|CaseType|1"
}
},
EncounterOpportunityLevel = OpportunityLevel.Organization
};
opportunity.ConfigurationGuid = framework.Configurations.FirstOrDefault().ConfigurationGuid;
var service = new ENOpportunityService(framework, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
var results = await service.CreateAsync(opportunity.MapToDataModel(null), Guid.Empty, true, CancellationToken.None);
results.Should().NotBe(Guid.Empty);
}
[Test]
public async Task TestCreateAsyncAllCaseTypesOrganization()
{
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var opportunity = new ENOpportunityDataModelWithFilters
{
EncounterGroup = 0,
EncounterOpportunityLevel = OpportunityLevel.Organization
};
opportunity.ConfigurationGuid = framework.Configurations.FirstOrDefault().ConfigurationGuid;
var service = new ENOpportunityService(framework, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
var results = await service.CreateAsync(opportunity.MapToDataModel(null), Guid.Empty, true, CancellationToken.None);
results.Should().NotBe(Guid.Empty);
}
[TestCaseSource(nameof(GetENOpportunityDataModel))]
public async Task TestCreateAsync(ENOpportunityDataModelWithFilters opportunity)
{
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
opportunity.ConfigurationGuid = framework.Configurations.FirstOrDefault().ConfigurationGuid;
var service = new ENOpportunityService(framework, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
var results = await service.CreateAsync(opportunity.MapToDataModel(null), Guid.Empty, true, CancellationToken.None);
results.Should().NotBe(Guid.Empty);
}
[Test, Ignore("Problem moq'ing GetEntityNodes")]
public async Task TestCreateAsyncAllCaseTypesAllEntities()
{
var serviceMock = new Mock<ENOpportunityService>(_dbContextFactoryMock.Object, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
var service = serviceMock.Object;
var opportunity = new ENOpportunityDataModelWithFilters
{
EncounterGroup = 0,
EncounterOpportunityLevel = OpportunityLevel.Entity
};
var results = await service.CreateAsync(opportunity.MapToDataModel(null), Guid.Empty, true, CancellationToken.None);
results.Should().NotBe(Guid.Empty);
}
[Test]
public async Task TestGetEntityNodes()
{
var service = new ENOpportunityService(_dbContextFactoryMock.Object, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
var configuration = new Biz.Configuration.Configuration
{
DepartmentRollupGlobalId = ""
};
var results = await service.GetEntityNodes(configuration, CancellationToken.None);
results.Should().NotBeEmpty();
}
[Test]
public async Task TestGetServiceLineNodesBySg2()
{
var service = new ENOpportunityService(_dbContextFactoryMock.Object, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
var configuration = new Biz.Configuration.Configuration
{
ServiceLineDimensionGuid = Guid.Empty
};
var results = await service.GetServiceLineNodes(configuration, new List<string>(), CancellationToken.None);
results.Should().NotBeEmpty();
}
[Test]
public async Task TestGetServiceLineNodes()
{
var service = new ENOpportunityService(_dbContextFactoryMock.Object, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
var configuration = new Biz.Configuration.Configuration
{
ServiceLineDimensionGuid = Guid.NewGuid()
};
var results = await service.GetServiceLineNodes(configuration, new List<string>(), CancellationToken.None);
results.Should().NotBeEmpty();
}
[Test]
public async Task TestGetNodesByGlobalId()
{
var service = new ENOpportunityService(_dbContextFactoryMock.Object, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
var results = await service.GetNodesByGlobalId("entity", CancellationToken.None);
results.Should().NotBeEmpty();
}
[Test]
public async Task TestGetNodesByHierarchyGuid()
{
var service = new ENOpportunityService(_dbContextFactoryMock.Object, null, null,
_schemaServiceMock.Object, _hangfireServiceMock.Object, _configurationMock.Object, _fiscalMonthResolverMock.Object,
_initiativeService, _updateFactOpportunitySavingsService, _backgroundJobClient.Object, _idServiceClient.Object, _notificationHubContext.Object, _authorizationServiceClient.Object);
var results = await service.GetNodesByHierarchyGuid(Guid.NewGuid(), CancellationToken.None);
results.Should().NotBeEmpty();
}
[Test]
public async Task TestSetHidden()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var opportunities = framework.ENOpportunities;
var opportunity = opportunities.First(vo => !vo.IsHidden);
var isHidden = opportunity.IsHidden;
var service = new ENOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
await service.SetHiddenAsync(opportunity.OpportunityGuid, !isHidden, CancellationToken.None);
// assert
var result = await service.GetById(opportunity.OpportunityGuid, CancellationToken.None);
result.IsHidden.Should().Be(!isHidden);
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestGetFilterOptionsAsync(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var opportunityService = new ENOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
// act
var taskResult = await opportunityService
.GetFilterOptionsAsync(new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345"), cancellationToken);
// assert
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName))
{
taskResult.Should().BeNull();
return;
}
taskResult.Should()
.BeOfType<ENFilterOptions>()
.And.NotBeNull();
taskResult.OpportunityLevels.Should().AllBeOfType<FilterMember>().And.NotBeNullOrEmpty()
.And.NotContain(cd => string.IsNullOrEmpty(cd.Id))
.And.NotContain(cd => string.IsNullOrEmpty(cd.Text));
}
catch (NotImplementedException nie)
{
nie.Should().BeOfType<NotImplementedException>();
}
catch (Exception e)
{
e.Should().BeOfType<OperationCanceledException>();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestSetHiddenAsync(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var encounterOpportunityService = new ENOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
var opportunities = encounterOpportunityService.GetAllAsync(cancellationToken).Result;
var opportunity = opportunities.FirstOrDefault(o => o.IsHidden);
if (opportunity == null) return;
var isHidden = opportunity.IsHidden;
//act
await encounterOpportunityService.SetHiddenAsync(opportunity.OpportunityGuid, !isHidden,
cancellationToken);
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName)) return;
var result = encounterOpportunityService.GetById(opportunity.OpportunityGuid, cancellationToken).Result;
result.IsHidden.Should().Be(!isHidden);
}
catch (Exception e)
{
switch (e.GetType().Name)
{
case "AggregateException":
case "OperationCanceledException":
return;
}
e.Should().NotBeOfType<Exception>();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestSetHiddenAsync_NoOpportunity(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var opportunity = jazzEntityFrameworkFactory.ENOpportunities.FirstOrDefault(o => o.IsHidden);
if (opportunity == null) return;
var isHidden = opportunity.IsHidden;
var encounterOpportunityService = new ENOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
// act
await encounterOpportunityService.SetHiddenAsync(Guid.Empty, !isHidden,
cancellationToken);
}
catch (Exception e)
{
e.Should().BeOfType<OperationCanceledException>();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestSetNoteAsync(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var encounterOpportunityService = new ENOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
var opportunities = encounterOpportunityService.GetAllAsync(cancellationToken).Result;
var opportunity = opportunities.FirstOrDefault(o => o.Note == string.Empty);
if (opportunity == null) return;
var note = "new test note";
// act
await encounterOpportunityService.SetNoteAsync(opportunity.OpportunityGuid, note,
CancellationToken.None);
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName)) return;
var result = encounterOpportunityService.GetById(opportunity.OpportunityGuid, cancellationToken).Result;
result.Note.Should().Be(note);
// assert
}
catch (Exception e)
{
switch (e.GetType().Name)
{
case "AggregateException":
case "OperationCanceledException":
return;
}
e.Should().NotBeOfType<Exception>();
}
}
[Test]
public async Task TestSetNote()
{
// arrange
var framework = new JazzEntityFrameworkFactory();
await framework.CreateDbContextAsync(CancellationToken.None);
var opportunities = framework.ENOpportunities;
var opportunity = opportunities.First(vo => string.IsNullOrEmpty(vo.Note));
var note = opportunity.Note;
var expected = "Now is the time for all good engineers to write great code!";
var service = new ENOpportunityService(framework, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
await service.SetNoteAsync(opportunity.OpportunityGuid, expected, CancellationToken.None);
// assert
var result = await service.GetById(opportunity.OpportunityGuid, CancellationToken.None);
result.Note.Should().NotBeNullOrEmpty()
.And.NotBe(note)
.And.Be(expected);
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworksWithCancellation))]
public async Task TestSetNoteAsync_NoOpportunity(JazzEntityFrameworkFactory jazzEntityFrameworkFactory, CancellationToken cancellationToken)
{
// arrange
var opportunity = jazzEntityFrameworkFactory.ENOpportunities.FirstOrDefault(o => o.Note == string.Empty);
if (opportunity == null) return;
var note = "new test note";
var encounterOpportunityService = new ENOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
try
{
// act
await encounterOpportunityService.SetNoteAsync(Guid.Empty, note, cancellationToken);
// assert
}
catch (Exception e)
{
e.Should().BeOfType<OperationCanceledException>();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworks))]
public async Task TestGetAllAsync(JazzEntityFrameworkFactory jazzEntityFrameworkFactory)
{
// arrange
var encounterOpportunityService = new ENOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var taskResult = await encounterOpportunityService.GetAllAsync(CancellationToken.None);
// assert
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName))
{
taskResult.Should().BeNullOrEmpty();
}
else
{
taskResult.Should().NotBeNullOrEmpty();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworks))]
public async Task TestGetById(JazzEntityFrameworkFactory jazzEntityFrameworkFactory)
{
// arrange
var encounterOpportunityService = new ENOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var tempResult = await encounterOpportunityService.GetAllAsync(CancellationToken.None);
var taskResult =
await encounterOpportunityService.GetById(tempResult.FirstOrDefault()?.OpportunityGuid ?? Guid.Empty, CancellationToken.None);
// assert
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName))
{
taskResult.Should().BeNull();
}
else
{
taskResult.Should().NotBeNull()
.And.BeOfType<ENOpportunity>();
}
}
[TestCaseSource(typeof(JazzEntityFrameworkFactory), nameof(JazzEntityFrameworkFactory.GetFrameworks))]
public async Task TestGetByIds(JazzEntityFrameworkFactory jazzEntityFrameworkFactory)
{
// arrange
var encounterOpportunityService = new ENOpportunityService(jazzEntityFrameworkFactory, null, null, _initiativeService, _updateFactOpportunitySavingsService);
// act
var tempResult = await encounterOpportunityService.GetAllAsync(CancellationToken.None);
var ids = tempResult.Select(o => o.OpportunityGuid);
var taskResult = await encounterOpportunityService.GetByIds(ids, CancellationToken.None);
// assert
if (string.IsNullOrEmpty(jazzEntityFrameworkFactory.TestName))
{
taskResult.Should().BeNullOrEmpty();
}
else
{
taskResult.Should().NotBeNullOrEmpty()
.And.AllBeOfType<ENOpportunity>()
.And.HaveCount(ids.Count());
}
}
[TearDown]
public void TestTearDown()
{
// Method intentionally left empty.
}
#region TestCaseData
public static IEnumerable<TestCaseData> GetFilterData()
{
yield return new TestCaseData(new ENFilters { ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("No Filters");
yield return new TestCaseData(new ENFilters { ViewVisible = true, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("View Visible");
yield return new TestCaseData(new ENFilters { ViewVisible = false, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("Not View Visible");
yield return new TestCaseData(new ENFilters { IdentifiedSavings = 1999, ViewVisible = true, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName(
"By Identified Savings");
yield return new TestCaseData(new ENFilters { IdentifiedSavings = 999, ViewVisible = false, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName(
"By Identified Savings Not ViewVisible");
yield return new TestCaseData(new ENFilters { OpportunityLevels = new[] { "Entity" }, ViewVisible = true, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") })
.SetName("By OpportunityLevels");
yield return new TestCaseData(new ENFilters { OpportunityLevels = new[] { "Provider" }, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345"), ViewVisible = false })
.SetName("By OpportunityLevels Not ViewVisible");
yield return new TestCaseData(new ENFilters { EncounterGroups = EncounterGroup.CaseType, ViewVisible = true, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") })
.SetName("By EncounterGroups");
yield return new TestCaseData(new ENFilters { EncounterGroups = EncounterGroup.PatientPopulation, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345"), ViewVisible = false })
.SetName("By EncounterGroups Not ViewVisible");
yield return new TestCaseData(new ENFilters { Seasonality = SeasonalityType.With, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") })
.SetName("By Seaonality");
yield return new TestCaseData(new ENFilters { OpportunitySearch = "EN - 101", ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("Search By Id");
yield return new TestCaseData(new ENFilters { ExcludeInitiatives = true, ConfigurationGuid = new Guid("e96abe3e-1526-43ca-815e-ee30c5b34345") }).SetName("Exclude Opportunities with Initiatives");
}
public static IEnumerable<TestCaseData> GetENOpportunityDataModel()
{
return from configGuid in ConfigGuids
from EncounterGroup encounterGroup in Enum.GetValues(typeof(EncounterGroup))
where encounterGroup != EncounterGroup.Both
from OpportunityLevel encounterOpportunityLevel in Enum.GetValues(typeof(OpportunityLevel))
from encounterGroups in new[] { true, false }
from opportunityLevels in new[] { true, false }
select new TestCaseData(new ENOpportunityDataModelWithFilters
{
ConfigurationGuid = configGuid,
EncounterGroup = (int)encounterGroup,
EncounterOpportunityLevel = encounterOpportunityLevel,
CaseTypes = encounterGroup == EncounterGroup.CaseType
? new List<FilterHelper>
{
new() {Id = "Id"}
}
: new List<FilterHelper>(),
PatientPopulations = encounterGroup == EncounterGroup.PatientPopulation
? new List<FilterHelper>
{
new () {Id = "Id"}
}
: new List<FilterHelper>(),
Entities = encounterOpportunityLevel == OpportunityLevel.Entity
? new List<FilterHelper>
{
new () {Id = "Id"}
}
: new List<FilterHelper>(),
ServiceLines = encounterOpportunityLevel == OpportunityLevel.ServiceLine
? new List<FilterHelper>
{
new () {Id = "Id"}
}
: new List<FilterHelper>(),
Specialties = encounterOpportunityLevel == OpportunityLevel.Specialty
? new List<FilterHelper>
{
new () {Id = "Id"}
}
: new List<FilterHelper>(),
Providers = encounterOpportunityLevel == OpportunityLevel.Provider
? new List<FilterHelper>
{
new () {Id = "Id"}
}
: new List<FilterHelper>()
}).SetName($"{nameof(TestCreateAsync)} {ConfigNames[ConfigGuids.IndexOf(configGuid)]} IsUsingCustomEntityDim: {ConfigGuids.IndexOf(configGuid) == 1} IsUsingSg2: {ConfigGuids.IndexOf(configGuid) == 3} {encounterGroup} {encounterOpportunityLevel}");
}
#endregion
}
}
@@ -0,0 +1,297 @@
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake;
using Strata.ContinuousImprovement.Biz.Exploration;
using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies;
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.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.CoreLib.Claims;
using Strata.StrataSphereCompare.Client;
using Strata.StrataSphereCompare.Client.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static Strata.ContinuousImprovement.Biz.Exploration.ExplorationService;
using ExplorationFilter = Strata.ContinuousImprovement.Biz.Exploration.Filters.Filter;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Exploration
{
[TestFixture, Category("Unit")]
public class ExplorationServiceBenchmarkingUnitTests
{
private const string FeatureFlagKey = "cibenchmarkingenabled";
private const string CaseTypeFamilyName = "Sample CTF";
private Mock<ISnowflakeDatabaseContext> _snowflakeDatabaseContextMock;
private Mock<IFiscalMonthResolver> _fiscalMonthResolverMock;
private Mock<IClaimsPrincipalAccessor> _claimsPrincipalAccessorMock;
private Mock<IStrataSphereCompareService> _strataSphereCompareServiceMock;
private Mock<ILogger<ExplorationService>> _explorationServiceLoggerMock;
private Mock<IExplorationFilterService> _explorationFilterServiceMock;
private ExplorationInfo _explorationInfo;
[OneTimeSetUp]
public void OneTimeSetUp()
{
_snowflakeDatabaseContextMock = new Mock<ISnowflakeDatabaseContext>();
_fiscalMonthResolverMock = new Mock<IFiscalMonthResolver>();
_explorationServiceLoggerMock = new Mock<ILogger<ExplorationService>>();
var principal = TestUtilities.GetClaimsPrincipalAccessor();
var defaultUser = principal.GetCurrentClaimsPrincipal();
_claimsPrincipalAccessorMock = new Mock<IClaimsPrincipalAccessor>();
_claimsPrincipalAccessorMock.Setup(a => a.GetCurrentClaimsPrincipal()).Returns(defaultUser);
_strataSphereCompareServiceMock = new Mock<IStrataSphereCompareService>();
_explorationFilterServiceMock = new Mock<IExplorationFilterService>();
// Setup ExplorationFilterService mock
_explorationFilterServiceMock
.Setup(s => s.GetFilterStringsAsync(It.IsAny<IEnumerable<FilterChipItem>>(), It.IsAny<IQueryParamBase>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(("", ""));
_explorationFilterServiceMock
.Setup(s => s.BuildDataFiltersAsync(It.IsAny<CaseTypeFamily>(), It.IsAny<string>(), It.IsAny<ExplorationFilter>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new CostDetailsFilterDto());
// Setup 1: For GetCaseTypeFamilySummariesAsync (no drill down filter)
_strataSphereCompareServiceMock
.Setup(s => s.GetCostDetailsAsync(
It.IsAny<BaseComparisonParams>(),
It.Is<CostDetailsFilterDto>(filter => filter.CostDetailsFilterDrillDown == null),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new[]
{
new CostDetailsDto
{
AprDrgCaseTypeFamilyName = CaseTypeFamilyName,
P50th_VariableDirectCost = 250.75m,
P50th_Alos = 5.2m
}
});
// Setup 2: For GetCaseTypeFamilySummaryDetailsAsync (with drill down filter)
_strataSphereCompareServiceMock
.Setup(s => s.GetCostDetailsAsync(
It.IsAny<BaseComparisonParams>(),
It.Is<CostDetailsFilterDto>(filter => filter.CostDetailsFilterDrillDown != null),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new[]
{
new CostDetailsDto
{
SphChargeCodeCostDriver = "LOS",
P50th_VariableDirectCost = 150.50m,
P50th_Alos = 3.2m
},
new CostDetailsDto
{
SphChargeCodeCostDriver = "Surgery",
P50th_VariableDirectCost = 300.25m,
P50th_Alos = 4.8m
}
});
// Summary Mock Data
_snowflakeDatabaseContextMock
.Setup(s => s.QueryAsync<CaseTypeFamilySummaryInfo>(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<object>(), It.IsAny<TimeSpan>()))
.ReturnsAsync(new[]
{
new CaseTypeFamilySummaryInfo { CaseTypeFamilyName = CaseTypeFamilyName }
});
// Summary Detail Mock Data - for GetCaseTypeFamilySummaryDetailsAsync
_snowflakeDatabaseContextMock
.Setup(s => s.QueryAsync<CaseTypeFamilyEncounterInfo>(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<object>(), It.IsAny<TimeSpan>()))
.ReturnsAsync(new[]
{
new CaseTypeFamilyEncounterInfo
{
CaseTypeFamilyName = CaseTypeFamilyName,
CostDriver = "LOS"
},
new CaseTypeFamilyEncounterInfo
{
CaseTypeFamilyName = CaseTypeFamilyName,
CostDriver = "Surgery"
}
});
_explorationInfo = new ExplorationCostDriverInfo
{
CaseTypeFamilyId = 1,
PeerGroupId = 1,
ExclusionCriteria = new ExclusionCriteria
{
LosExclusionType = ExclusionCriteriaType.None,
CostExclusionType = ExclusionCriteriaType.None
},
Filters = new ExplorationFilter // Use the alias
{
FilterChipItems = new List<FilterChipItem>
{
new FilterChipItem
{
ChipType = ChipType.DateRange,
Key = ChipKey.DischargeDateID,
DateRange = new List<DateTime>
{
new DateTime(2024, 1, 1),
new DateTime(2024, 12, 31)
}
}
}
}
};
}
[Test]
public async Task GetCaseTypeFamilySummariesAsync_FeatureFlagOn_PopulatesPeerMetrics()
{
// Arrange
var jazzFactory = new JazzEntityFrameworkFactory();
await jazzFactory.CreateDbContextAsync(CancellationToken.None);
var featureFlagWrapper = Mock.Of<IFeatureFlagWrapper>();
Mock.Get(featureFlagWrapper)
.Setup(f => f.IsCiBenchmarkingEnabled(It.IsAny<bool>()))
.ReturnsAsync(true);
var service = new ExplorationService(
jazzFactory,
_snowflakeDatabaseContextMock.Object,
_fiscalMonthResolverMock.Object,
featureFlagWrapper,
_strataSphereCompareServiceMock.Object,
_explorationFilterServiceMock.Object,
_explorationServiceLoggerMock.Object);
// Act
var summaries = await service.GetCaseTypeFamilySummariesAsync(_explorationInfo, CancellationToken.None);
var summary = summaries.Single(s => s.Name == CaseTypeFamilyName);
// Assert
Assert.That(summary.Data.PeerMedianCost, Is.EqualTo(250.75d));
Assert.That(summary.Data.PeerMedianALos, Is.EqualTo(5.2d));
}
[Test]
public async Task GetCaseTypeFamilySummariesAsync_FeatureFlagOff_DefaultPeerMetrics()
{
// Arrange
var jazzFactory = new JazzEntityFrameworkFactory();
await jazzFactory.CreateDbContextAsync(CancellationToken.None);
var featureFlagWrapper = Mock.Of<IFeatureFlagWrapper>();
Mock.Get(featureFlagWrapper)
.Setup(f => f.IsCiBenchmarkingEnabled(It.IsAny<bool>()))
.ReturnsAsync(false);
var service = new ExplorationService(
jazzFactory,
_snowflakeDatabaseContextMock.Object,
_fiscalMonthResolverMock.Object,
featureFlagWrapper,
_strataSphereCompareServiceMock.Object,
_explorationFilterServiceMock.Object,
_explorationServiceLoggerMock.Object);
// Act
var summaries = await service.GetCaseTypeFamilySummariesAsync(_explorationInfo, CancellationToken.None);
var summary = summaries.Single(s => s.Name == CaseTypeFamilyName);
// Assert
Assert.That(summary.Data.PeerMedianCost, Is.EqualTo(0d));
Assert.That(summary.Data.PeerMedianALos, Is.EqualTo(0d));
}
[Test, Ignore("Feature flag is not enabled, this might need additional refactoring")]
public async Task GetCaseTypeFamilySummaryDetailsAsync_FeatureFlagOn_PopulatesPeerMetrics()
{
// Arrange
var jazzFactory = new JazzEntityFrameworkFactory();
await jazzFactory.CreateDbContextAsync(CancellationToken.None);
var featureFlagWrapper = Mock.Of<IFeatureFlagWrapper>();
Mock.Get(featureFlagWrapper)
.Setup(f => f.IsCiBenchmarkingEnabled(It.IsAny<bool>()))
.ReturnsAsync(true);
var service = new ExplorationService(
jazzFactory,
_snowflakeDatabaseContextMock.Object,
_fiscalMonthResolverMock.Object,
featureFlagWrapper,
_strataSphereCompareServiceMock.Object,
_explorationFilterServiceMock.Object,
_explorationServiceLoggerMock.Object);
// Act
var summaries = await service.GetCaseTypeFamilySummaryDetailsAsync(_explorationInfo, CancellationToken.None);
var summary = summaries.Single();
// Assert
Assert.That(summary, Is.Not.Null);
Assert.That(summary.Name, Is.EqualTo(CaseTypeFamilyName));
Assert.That(summary.Children, Is.Not.Null.And.Not.Empty);
var losChild = summary.Children.FirstOrDefault(c => c.Name == "LOS");
if (losChild != null)
{
Assert.That(losChild.Data.PeerMedianCost, Is.EqualTo(150.50d));
}
var surgeryChild = summary.Children.FirstOrDefault(c => c.Name == "Surgery");
if (surgeryChild != null)
{
Assert.That(surgeryChild.Data.PeerMedianCost, Is.EqualTo(300.25d));
}
}
[Test]
public async Task GetCaseTypeFamilySummaryDetailsAsync_FeatureFlagOff_DefaultPeerMetrics()
{
// Arrange
var jazzFactory = new JazzEntityFrameworkFactory();
await jazzFactory.CreateDbContextAsync(CancellationToken.None);
var featureFlagWrapper = Mock.Of<IFeatureFlagWrapper>();
Mock.Get(featureFlagWrapper)
.Setup(f => f.IsCiBenchmarkingEnabled(It.IsAny<bool>()))
.ReturnsAsync(false);
var service = new ExplorationService(
jazzFactory,
_snowflakeDatabaseContextMock.Object,
_fiscalMonthResolverMock.Object,
featureFlagWrapper,
_strataSphereCompareServiceMock.Object,
_explorationFilterServiceMock.Object,
_explorationServiceLoggerMock.Object);
// Act
var summaries = await service.GetCaseTypeFamilySummaryDetailsAsync(_explorationInfo as ExplorationCostDriverInfo, CancellationToken.None);
var summary = summaries.Single();
// Assert
Assert.That(summary, Is.Not.Null);
Assert.That(summary.Name, Is.EqualTo(CaseTypeFamilyName));
if (summary.Children != null && summary.Children.Any())
{
foreach (var child in summary.Children)
{
Assert.That(child.Data.PeerMedianCost, Is.EqualTo(0d));
Assert.That(child.Data.PeerMedianALos, Is.EqualTo(0d));
}
}
}
}
}
@@ -0,0 +1,316 @@
using DocumentFormat.OpenXml.Wordprocessing;
using Microsoft.Extensions.Logging;
using Moq;
using MoreLinq;
using NUnit.Framework;
using Strata.ContinuousImprovement.Biz.Configuration;
using Strata.ContinuousImprovement.Biz.DbContexts;
using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake;
using Strata.ContinuousImprovement.Biz.Exploration;
using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies;
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.ContinuousImprovement.JazzEntityFrameworkStub;
using Strata.SqlTools.Configuration.Common.AsyncFactory;
using Strata.StrataSphereCompare.Client;
using Strata.StrataSphereCompare.Client.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using VerifyNUnit;
using VerifyTests;
using static Strata.ContinuousImprovement.Biz.Exploration.ExplorationService;
using ExplorationFilter = Strata.ContinuousImprovement.Biz.Exploration.Filters.Filter;
namespace Strata.ContinuousImprovement.Biz.Test.Unit.Exploration
{
[TestFixture, Category("Unit")]
public class ExplorationServiceTest
{
private const string SourceFilePath = @"Exploration/source/";
public IAsyncDbContextFactory<JazzDbContext> DbContextFactory { get; set; }
public JazzEntityFrameworkFactory JAZZ_Framework { get; set; }
public JazzDbContext JazzDbContext { get; set; }
public ISqlGridReaderWrapper SqlGridReader { get; set; }
public ISnowflakeDatabaseContext SnowflakeDatabaseContext { get; set; }
public IConfigurationService ConfigurationService { get; set; }
public IFiscalMonthResolver FiscalMonthResolver { get; set; }
public ILogger<ExplorationService> Logger { get; set; }
public VerifySettings VerifySettings { get; set; }
public IFeatureFlagWrapper FeatureFlagWrapper { get; set; }
public IStrataSphereCompareService StrataSphereCompareService { get; set; }
public IExplorationFilterService ExplorationFilterService { get; set; }
[OneTimeSetUp]
public void ExplorationServiceTestOneTimeSetUp()
{
DbContextFactory = Mock.Of<IAsyncDbContextFactory<JazzDbContext>>();
JAZZ_Framework = new JazzEntityFrameworkFactory();
Mock.Get(DbContextFactory)
.Setup(s => s.CreateDbContextAsync(It.IsAny<CancellationToken>()))
.Returns(async () =>
{
JazzDbContext = await JAZZ_Framework.CreateDbContextAsync(CancellationToken.None);
return JazzDbContext;
});
SqlGridReader = Mock.Of<ISqlGridReaderWrapper>();
Mock.Get(SqlGridReader)
.Setup(s => s.Read(It.IsAny<bool>()))
.Returns(Enumerable.Empty<dynamic>());
SnowflakeDatabaseContext = Mock.Of<ISnowflakeDatabaseContext>();
Mock.Get(SnowflakeDatabaseContext)
.Setup(s => s.QueryMultipleAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<object>(), It.IsAny<TimeSpan>()))
.Returns(Task.FromResult(SqlGridReader));
ConfigurationService = Mock.Of<IConfigurationService>();
FiscalMonthResolver = Mock.Of<IFiscalMonthResolver>();
Logger = Mock.Of<ILogger<ExplorationService>>();
VerifySettings = TestExtensions.TestSettings();
FeatureFlagWrapper = Mock.Of<IFeatureFlagWrapper>();
StrataSphereCompareService = Mock.Of<IStrataSphereCompareService>();
ExplorationFilterService = Mock.Of<IExplorationFilterService>();
// Setup the mock to return empty results
Mock.Get(ExplorationFilterService)
.Setup(s => s.GetFilterStringsAsync(It.IsAny<IEnumerable<FilterChipItem>>(), It.IsAny<IQueryParamBase>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(("", ""));
Mock.Get(ExplorationFilterService)
.Setup(s => s.BuildDataFiltersAsync(It.IsAny<CaseTypeFamily>(), It.IsAny<string>(), It.IsAny<ExplorationFilter>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new CostDetailsFilterDto());
Mock.Get(ExplorationFilterService)
.Setup(s => s.CreateCostDriverFilter())
.Returns(new FilterChipItem());
Mock.Get(FeatureFlagWrapper)
.Setup(f => f.IsCiBenchmarkingEnabled(It.IsAny<bool>()))
.ReturnsAsync(true);
}
[TestCaseSource(nameof(GetCaseTypeFamilyDetailsTestCases))]
public async Task GetCaseTypeFamilyDetailsTest(GetCaseTypeFamilyDetailsTestData data)
{
// arrange
Mock.Get(SqlGridReader)
.Setup(s => s.Read<CTFGroupCalcs>(It.IsAny<bool>()))
.Returns(data.CTFGroupCalcsList);
Mock.Get(SqlGridReader)
.Setup(s => s.Read<EntityGroupCalcs>(It.IsAny<bool>()))
.Returns(data.EntityGroupCalcsList);
Mock.Get(SqlGridReader)
.Setup(s => s.Read<EntityCodeGroupCalcs>(It.IsAny<bool>()))
.Returns(data.EntityCodeGroupCalcsList);
Mock.Get(SqlGridReader)
.Setup(s => s.Read<EntityCodePhysicianGroupCalcs>(It.IsAny<bool>()))
.Returns(data.EntityCodePhysicianGroupCalcsList);
Mock.Get(SqlGridReader)
.Setup(s => s.Read<CTFEncounterDetail>(It.IsAny<bool>()))
.Returns(data.CTFEncounterDetailList);
var explorationService = new ExplorationService(
DbContextFactory,
SnowflakeDatabaseContext,
FiscalMonthResolver,
FeatureFlagWrapper,
StrataSphereCompareService,
ExplorationFilterService,
Logger);
// act
var taskResult = await explorationService.GetCaseTypeFamilyDetailsAsync(data.ExplorationDetailInfoData, CancellationToken.None);
// assert
await Verifier.Verify(taskResult, VerifySettings)
.UseFileName(TestContext.CurrentContext.TestName());
}
[TestCaseSource(nameof(GetEncountersTestCases))]
public async Task GetEncountersTest(GetEncountersTestData data)
{
// arrange
Mock.Get(SqlGridReader)
.Setup(s => s.Read<PhysicianInfo>(It.IsAny<bool>()))
.Returns(data.PhysicianInfoList);
Mock.Get(SqlGridReader)
.Setup(s => s.Read<EncounterInfoBase>(It.IsAny<bool>()))
.Returns(data.EncounterInfoBaseList);
var configurationGuid = Guid.Empty;
var explorationService = new ExplorationService(
DbContextFactory,
SnowflakeDatabaseContext,
FiscalMonthResolver,
FeatureFlagWrapper,
StrataSphereCompareService,
ExplorationFilterService,
Logger);
// act
var taskResult = await explorationService.GetEncountersAsync(data.ExplorationDetailInfoData, configurationGuid, CancellationToken.None);
// assert
await Verifier.Verify(taskResult, VerifySettings)
.UseFileName(TestContext.CurrentContext.TestName());
}
private static IEnumerable<TestCaseData> GetCaseTypeFamilyDetailsTestCases()
{
var testName = "GetCaseTypeFamilyDetailsTest";
var explorationDetailInfoDataJson = GetSourceFile("ExplorationDetailInfo.txt");
var ctfGroupCalcsListJson = GetSourceFile("CTFGroupCalcs.txt");
var entityGroupCalcsListJson = GetSourceFile("EntityGroupCalcs.txt");
var entityCodeGroupCalcsListJson = GetSourceFile("EntityCodeGroupCalcs.txt");
var entityCodePhysicianGroupCalcsListJson = GetSourceFile("EntityCodePhysicianGroupCalcs.txt");
var ctfEncounterDetailListJson = GetSourceFile("CTFEncounterDetail.txt");
var dataAllMatch = new GetCaseTypeFamilyDetailsTestData()
{
ExplorationDetailInfoData = JsonSerializer.Deserialize<ExplorationDetailInfo>(explorationDetailInfoDataJson),
CTFGroupCalcsList = JsonSerializer.Deserialize<IEnumerable<CTFGroupCalcs>>(ctfGroupCalcsListJson),
EntityGroupCalcsList = JsonSerializer.Deserialize<IEnumerable<EntityGroupCalcs>>(entityGroupCalcsListJson),
EntityCodeGroupCalcsList = JsonSerializer.Deserialize<IEnumerable<EntityCodeGroupCalcs>>(entityCodeGroupCalcsListJson),
EntityCodePhysicianGroupCalcsList = JsonSerializer.Deserialize<IEnumerable<EntityCodePhysicianGroupCalcs>>(entityCodePhysicianGroupCalcsListJson),
CTFEncounterDetailList = JsonSerializer.Deserialize<IEnumerable<CTFEncounterDetail>>(ctfEncounterDetailListJson)
};
dataAllMatch.ExplorationDetailInfoData.Filters = new ExplorationFilter() // FIXED: Changed from Biz.Exploration.Filters.Filter to ExplorationFilter
{
FilterChipItems = new List<FilterChipItem>()
{
new FilterChipItem {
ChipType = ChipType.DateRange, Key = ChipKey.DischargeDateID,
DateRange = new List<DateTime>()
{
new DateTime(2024, 1, 1),
new DateTime(2024, 12, 31)
}
}
}
};
yield return new TestCaseData(dataAllMatch)
.SetName($"{testName} All Match");
var dataPhysicianIdNotMatch = new GetCaseTypeFamilyDetailsTestData()
{
ExplorationDetailInfoData = dataAllMatch.ExplorationDetailInfoData,
CTFGroupCalcsList = dataAllMatch.CTFGroupCalcsList,
EntityGroupCalcsList = dataAllMatch.EntityGroupCalcsList,
EntityCodeGroupCalcsList = dataAllMatch.EntityCodeGroupCalcsList,
EntityCodePhysicianGroupCalcsList = JsonSerializer.Deserialize<IEnumerable<EntityCodePhysicianGroupCalcs>>(entityCodePhysicianGroupCalcsListJson),
CTFEncounterDetailList = dataAllMatch.CTFEncounterDetailList
};
dataPhysicianIdNotMatch.EntityCodePhysicianGroupCalcsList.First().PhysicianId = 0;
yield return new TestCaseData(dataPhysicianIdNotMatch)
.SetName($"{testName} PhysicianId Not Match");
var dataCodeNotMatch = new GetCaseTypeFamilyDetailsTestData()
{
ExplorationDetailInfoData = dataAllMatch.ExplorationDetailInfoData,
CTFGroupCalcsList = dataAllMatch.CTFGroupCalcsList,
EntityGroupCalcsList = dataAllMatch.EntityGroupCalcsList,
EntityCodeGroupCalcsList = JsonSerializer.Deserialize<IEnumerable<EntityCodeGroupCalcs>>(entityCodeGroupCalcsListJson),
EntityCodePhysicianGroupCalcsList = dataAllMatch.EntityCodePhysicianGroupCalcsList,
CTFEncounterDetailList = dataAllMatch.CTFEncounterDetailList
};
dataCodeNotMatch.EntityCodeGroupCalcsList.First().Code = "";
yield return new TestCaseData(dataCodeNotMatch)
.SetName($"{testName} Code Not Match");
var dataEntityNotMatch = new GetCaseTypeFamilyDetailsTestData()
{
ExplorationDetailInfoData = dataAllMatch.ExplorationDetailInfoData,
CTFGroupCalcsList = dataAllMatch.CTFGroupCalcsList,
EntityGroupCalcsList = JsonSerializer.Deserialize<IEnumerable<EntityGroupCalcs>>(entityGroupCalcsListJson),
EntityCodeGroupCalcsList = dataAllMatch.EntityCodeGroupCalcsList,
EntityCodePhysicianGroupCalcsList = dataAllMatch.EntityCodePhysicianGroupCalcsList,
CTFEncounterDetailList = dataAllMatch.CTFEncounterDetailList
};
dataEntityNotMatch.EntityGroupCalcsList.First().EntityId = 0;
yield return new TestCaseData(dataEntityNotMatch)
.SetName($"{testName} Entity Not Match");
}
private static IEnumerable<TestCaseData> GetEncountersTestCases()
{
var testName = "GetEncountersTest";
var explorationDetailInfoDataJson = GetSourceFile("ExplorationDetailInfo.txt");
var physicianInfoJson = GetSourceFile("PhysicianInfo.txt");
var encounterInfoBaseJson = GetSourceFile("EncounterInfoBase.txt");
var dataAllMatch = new GetEncountersTestData()
{
ExplorationDetailInfoData = JsonSerializer.Deserialize<ExplorationDetailInfo>(explorationDetailInfoDataJson),
PhysicianInfoList = JsonSerializer.Deserialize<IEnumerable<PhysicianInfo>>(physicianInfoJson),
EncounterInfoBaseList = JsonSerializer.Deserialize<IEnumerable<EncounterInfoBase>>(encounterInfoBaseJson)
};
dataAllMatch.ExplorationDetailInfoData.Filters = new ExplorationFilter() // FIXED: Changed from Biz.Exploration.Filters.Filter to ExplorationFilter
{
FilterChipItems = new List<FilterChipItem>()
{
new FilterChipItem {
ChipType = ChipType.DateRange, Key = ChipKey.DischargeDateID,
DateRange = new List<DateTime>()
{
new DateTime(2024, 1, 1),
new DateTime(2024, 12, 31)
}
}
}
};
yield return new TestCaseData(dataAllMatch)
.SetName($"{testName} All Match");
var dataPhysicianIdNotMatch = new GetEncountersTestData()
{
ExplorationDetailInfoData = dataAllMatch.ExplorationDetailInfoData,
PhysicianInfoList = JsonSerializer.Deserialize<IEnumerable<PhysicianInfo>>(physicianInfoJson),
EncounterInfoBaseList = dataAllMatch.EncounterInfoBaseList,
};
dataPhysicianIdNotMatch.PhysicianInfoList.First().PhysicianID = 0;
yield return new TestCaseData(dataPhysicianIdNotMatch)
.SetName($"{testName} PhysicianId Not Match");
}
private static string GetSourceFile(string file)
{
string filePath = Path.Combine(TestHelper.TestsRootDirectory, SourceFilePath, file);
string jsonData = File.ReadAllText(filePath);
return jsonData;
}
public class GetCaseTypeFamilySummariesTestData
{
public ExplorationInfo ExplorationInfoData { get; set; }
public IEnumerable<CaseTypeFamilyEncounterInfo> CaseTypeFamilyEncounterInfoList { get; set; }
}
public class GetCaseTypeFamilyDetailsTestData
{
public ExplorationDetailInfo ExplorationDetailInfoData { get; set; }
public IEnumerable<CTFGroupCalcs> CTFGroupCalcsList { get; set; }
public IEnumerable<EntityGroupCalcs> EntityGroupCalcsList { get; set; }
public IEnumerable<EntityCodeGroupCalcs> EntityCodeGroupCalcsList { get; set; }
public IEnumerable<EntityCodePhysicianGroupCalcs> EntityCodePhysicianGroupCalcsList { get; set; }
public IEnumerable<CTFEncounterDetail> CTFEncounterDetailList { get; set; }
}
public class GetEncountersTestData
{
public ExplorationDetailInfo ExplorationDetailInfoData { get; set; }
public IEnumerable<PhysicianInfo> PhysicianInfoList { get; set; }
public IEnumerable<EncounterInfoBase> EncounterInfoBaseList { get; set; }
}
}
}
@@ -0,0 +1,66 @@
[
{
"EncounterId": 24375411,
"EncounterRecordNumber": "1024375411",
"CTFType": 0,
"EntityId": 93,
"Code": "880",
"PhysicianId": 3339,
"LengthOfStay": 5,
"VariableDirectCost": 208.1365,
"IdentifiedVariation": 148.89435,
"Charges": 4045.0814,
"IsExpired": 0,
"IsExcluded": false,
"DischargeStatusCode": "06",
"DischargeStatusDescription": "Discharged/transferred to home under care of an organized home health service organization in anticipation of covered skilled care"
},
{
"EncounterId": 26713064,
"EncounterRecordNumber": "1026713064",
"CTFType": 0,
"EntityId": 93,
"Code": "880",
"PhysicianId": 3474,
"LengthOfStay": 3,
"VariableDirectCost": 71.8018,
"IdentifiedVariation": 12.559649999999998,
"Charges": 985.8658,
"IsExpired": 0,
"IsExcluded": false,
"DischargeStatusCode": "01",
"DischargeStatusDescription": "Discharged to home or self care (routine discharge)"
},
{
"EncounterId": 27090900,
"EncounterRecordNumber": "1027090900",
"CTFType": 0,
"EntityId": 93,
"Code": "880",
"PhysicianId": 794,
"LengthOfStay": 15,
"VariableDirectCost": 5.0037,
"IdentifiedVariation": -54.23845,
"Charges": 384.65,
"IsExpired": 0,
"IsExcluded": true,
"DischargeStatusCode": "03",
"DischargeStatusDescription": "Discharged/transferred to Skilled Nursing Facility (SNF) with Medicare certification"
},
{
"EncounterId": 28432227,
"EncounterRecordNumber": "1028432227",
"CTFType": 0,
"EntityId": 93,
"Code": "880",
"PhysicianId": 3896,
"LengthOfStay": 7,
"VariableDirectCost": 46.6825,
"IdentifiedVariation": -12.559650000000005,
"Charges": 996.8704,
"IsExpired": 0,
"IsExcluded": true,
"DischargeStatusCode": "03",
"DischargeStatusDescription": "Discharged/transferred to Skilled Nursing Facility (SNF) with Medicare certification"
}
]
@@ -0,0 +1,7 @@
[
{
"CTFId": 206,
"CTFName": "Acute Adjustment Reaction And Psychosocial Dysfunction",
"MedianVDC": 59.24215
}
]
@@ -0,0 +1,596 @@
[
{
"CaseTypeFamilyId": 6654,
"CaseTypeFamilyName": "Endoscopy Procedures on the Mediastinum",
"Vdc": 2038.5242,
"MedianVdc": 1931.0259,
"TwentyFivePercentileVdc": 1877.27675,
"SeventyFivePercentileVdc": 1984.77505,
"TwentyFivePercentileLos": 0,
"SeventyFivePercentileLos": 0,
"CostDriver": "Laboratory",
"EncounterId": 2562780,
"DischargeDateTime": "2023-01-17T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 270.5061,
"IsExpired": 0,
"LengthOfStay": 0,
"MedianLengthOfStay": 0,
"MedianVariableDirectCost": 283.5443,
"MedianGrossCharges": 0,
"VariableDirectCost": 554.0504,
"Charges": 11265.2698,
"IsExcluded": false,
"IqrLos": 0,
"IqrVdc": 107.4983,
"MadLos": 0,
"MadVdc": 159.37698,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 6654,
"CaseTypeFamilyName": "Endoscopy Procedures on the Mediastinum",
"Vdc": 2038.5242,
"MedianVdc": 1931.0259,
"TwentyFivePercentileVdc": 1877.27675,
"SeventyFivePercentileVdc": 1984.77505,
"TwentyFivePercentileLos": 0,
"SeventyFivePercentileLos": 0,
"CostDriver": "Excluded",
"EncounterId": 2562780,
"DischargeDateTime": "2023-01-17T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 0,
"IsExpired": 0,
"LengthOfStay": 0,
"MedianLengthOfStay": 0,
"MedianVariableDirectCost": 0.2644,
"MedianGrossCharges": 0,
"VariableDirectCost": 0.2644,
"Charges": 49.0752,
"IsExcluded": false,
"IqrLos": 0,
"IqrVdc": 107.4983,
"MadLos": 0,
"MadVdc": 159.37698,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 6654,
"CaseTypeFamilyName": "Endoscopy Procedures on the Mediastinum",
"Vdc": 2038.5242,
"MedianVdc": 1931.0259,
"TwentyFivePercentileVdc": 1877.27675,
"SeventyFivePercentileVdc": 1984.77505,
"TwentyFivePercentileLos": 0,
"SeventyFivePercentileLos": 0,
"CostDriver": "OR Time",
"EncounterId": 2562780,
"DischargeDateTime": "2023-01-17T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": -15.31945,
"IsExpired": 0,
"LengthOfStay": 0,
"MedianLengthOfStay": 0,
"MedianVariableDirectCost": 1066.93575,
"MedianGrossCharges": 0,
"VariableDirectCost": 1051.6163,
"Charges": 17348.9676,
"IsExcluded": false,
"IqrLos": 0,
"IqrVdc": 107.4983,
"MadLos": 0,
"MadVdc": 159.37698,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 6654,
"CaseTypeFamilyName": "Endoscopy Procedures on the Mediastinum",
"Vdc": 2038.5242,
"MedianVdc": 1931.0259,
"TwentyFivePercentileVdc": 1877.27675,
"SeventyFivePercentileVdc": 1984.77505,
"TwentyFivePercentileLos": 0,
"SeventyFivePercentileLos": 0,
"CostDriver": "Supplies",
"EncounterId": 2562780,
"DischargeDateTime": "2023-01-17T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": -74.0283,
"IsExpired": 0,
"LengthOfStay": 0,
"MedianLengthOfStay": 0,
"MedianVariableDirectCost": 275.0648,
"MedianGrossCharges": 0,
"VariableDirectCost": 201.0365,
"Charges": 908.8934,
"IsExcluded": false,
"IqrLos": 0,
"IqrVdc": 107.4983,
"MadLos": 0,
"MadVdc": 159.37698,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 6654,
"CaseTypeFamilyName": "Endoscopy Procedures on the Mediastinum",
"Vdc": 2038.5242,
"MedianVdc": 1931.0259,
"TwentyFivePercentileVdc": 1877.27675,
"SeventyFivePercentileVdc": 1984.77505,
"TwentyFivePercentileLos": 0,
"SeventyFivePercentileLos": 0,
"CostDriver": "Pharmacy",
"EncounterId": 2562780,
"DischargeDateTime": "2023-01-17T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": -15.66025,
"IsExpired": 0,
"LengthOfStay": 0,
"MedianLengthOfStay": 0,
"MedianVariableDirectCost": 120.94545,
"MedianGrossCharges": 0,
"VariableDirectCost": 105.2852,
"Charges": 1801.1414,
"IsExcluded": false,
"IqrLos": 0,
"IqrVdc": 107.4983,
"MadLos": 0,
"MadVdc": 159.37698,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 6654,
"CaseTypeFamilyName": "Endoscopy Procedures on the Mediastinum",
"Vdc": 2038.5242,
"MedianVdc": 1931.0259,
"TwentyFivePercentileVdc": 1877.27675,
"SeventyFivePercentileVdc": 1984.77505,
"TwentyFivePercentileLos": 0,
"SeventyFivePercentileLos": 0,
"CostDriver": "",
"EncounterId": 2562780,
"DischargeDateTime": "2023-01-17T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 0,
"IsExpired": 0,
"LengthOfStay": 0,
"MedianLengthOfStay": 0,
"MedianVariableDirectCost": 0,
"MedianGrossCharges": 0,
"VariableDirectCost": 0,
"Charges": 0,
"IsExcluded": false,
"IqrLos": 0,
"IqrVdc": 107.4983,
"MadLos": 0,
"MadVdc": 159.37698,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 6654,
"CaseTypeFamilyName": "Endoscopy Procedures on the Mediastinum",
"Vdc": 2038.5242,
"MedianVdc": 1931.0259,
"TwentyFivePercentileVdc": 1877.27675,
"SeventyFivePercentileVdc": 1984.77505,
"TwentyFivePercentileLos": 0,
"SeventyFivePercentileLos": 0,
"CostDriver": "Cardiovascular",
"EncounterId": 2562780,
"DischargeDateTime": "2023-01-17T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 0,
"IsExpired": 0,
"LengthOfStay": 0,
"MedianLengthOfStay": 0,
"MedianVariableDirectCost": 5.0037,
"MedianGrossCharges": 0,
"VariableDirectCost": 5.0037,
"Charges": 393.3321,
"IsExcluded": false,
"IqrLos": 0,
"IqrVdc": 107.4983,
"MadLos": 0,
"MadVdc": 159.37698,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 6654,
"CaseTypeFamilyName": "Endoscopy Procedures on the Mediastinum",
"Vdc": 2038.5242,
"MedianVdc": 1931.0259,
"TwentyFivePercentileVdc": 1877.27675,
"SeventyFivePercentileVdc": 1984.77505,
"TwentyFivePercentileLos": 0,
"SeventyFivePercentileLos": 0,
"CostDriver": "Therapeutic Services",
"EncounterId": 2562780,
"DischargeDateTime": "2023-01-17T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": -60.63385,
"IsExpired": 0,
"LengthOfStay": 0,
"MedianLengthOfStay": 0,
"MedianVariableDirectCost": 181.90155,
"MedianGrossCharges": 0,
"VariableDirectCost": 121.2677,
"Charges": 320.4913,
"IsExcluded": false,
"IqrLos": 0,
"IqrVdc": 107.4983,
"MadLos": 0,
"MadVdc": 159.37698,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "Pharmacy",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 0,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 373.0302,
"MedianGrossCharges": 0,
"VariableDirectCost": 373.0302,
"Charges": 11782.8149,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "Supplies",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": -19.7357,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 87.3104,
"MedianGrossCharges": 0,
"VariableDirectCost": 67.5747,
"Charges": 254.2575,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "OR Time",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 0,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 680.5711,
"MedianGrossCharges": 0,
"VariableDirectCost": 680.5711,
"Charges": 12771.463,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "LOS",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 357.1267,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 1747.5269,
"MedianGrossCharges": 0,
"VariableDirectCost": 2104.6536,
"Charges": 29186.0674,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "Other Diagnostic Services",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 37.18375,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 113.54845,
"MedianGrossCharges": 0,
"VariableDirectCost": 150.7322,
"Charges": 3224.1159,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 0,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 0,
"MedianGrossCharges": 0,
"VariableDirectCost": 0,
"Charges": 0,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "Laboratory",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 0,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 123.2207,
"MedianGrossCharges": 0,
"VariableDirectCost": 123.2207,
"Charges": 5503.8051,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "Therapeutic Services",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 245.3111,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 473.0037,
"MedianGrossCharges": 0,
"VariableDirectCost": 718.3148,
"Charges": 7026.8907,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "Imaging",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": 0,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 37.5413,
"MedianGrossCharges": 0,
"VariableDirectCost": 37.5413,
"Charges": 690.5777,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
},
{
"CaseTypeFamilyId": 592,
"CaseTypeFamilyName": "Skin Debridement",
"Vdc": 4353.6024,
"MedianVdc": 4353.6024,
"TwentyFivePercentileVdc": 2983.74705,
"SeventyFivePercentileVdc": 4525.49485,
"TwentyFivePercentileLos": 4.5,
"SeventyFivePercentileLos": 9.5,
"CostDriver": "Excluded",
"EncounterId": 2565425,
"DischargeDateTime": "2023-02-02T00:00:00Z",
"EncounterRecordNumber": null,
"EntityId": 0,
"IdentifiedVariation": -49.1141,
"IsExpired": 0,
"LengthOfStay": 12,
"MedianLengthOfStay": 7,
"MedianVariableDirectCost": 147.0779,
"MedianGrossCharges": 0,
"VariableDirectCost": 97.9638,
"Charges": 1718.5171,
"IsExcluded": false,
"IqrLos": 5,
"IqrVdc": 1541.7478,
"MadLos": 7.413,
"MadVdc": 509.6955,
"DischargeStatusCode": null,
"DischargeStatusDescription": null,
"MortalityRate": 0,
"ROM": 0,
"SOI": 0
}
]
@@ -0,0 +1,170 @@
[
{
"EncounterId": 3490618,
"EncounterRecordNumber": "1003490618",
"PhysicianID": 3845,
"AgeCohortID": 3,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 15,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-02-18T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 21268418,
"EncounterRecordNumber": "1021268418",
"PhysicianID": 3939,
"AgeCohortID": 4,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 2,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-04-07T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 21269053,
"EncounterRecordNumber": "1021269053",
"PhysicianID": 2986,
"AgeCohortID": 3,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 14,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-01-28T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 21366384,
"EncounterRecordNumber": "1021366384",
"PhysicianID": 1370,
"AgeCohortID": 4,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 12,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-03-01T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 21549774,
"EncounterRecordNumber": "1021549774",
"PhysicianID": 3153,
"AgeCohortID": 3,
"EntityID": 13,
"ServiceLineID": 13,
"LengthOfStay": 1,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-02-15T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 22931559,
"EncounterRecordNumber": "1022931559",
"PhysicianID": 4446,
"AgeCohortID": 4,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 11,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-05-21T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 23497650,
"EncounterRecordNumber": "1023497650",
"PhysicianID": 842,
"AgeCohortID": 4,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 8,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-05-14T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 24240851,
"EncounterRecordNumber": "1024240851",
"PhysicianID": 720,
"AgeCohortID": 3,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 3,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-06-24T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 25272685,
"EncounterRecordNumber": "1025272685",
"PhysicianID": 3481,
"AgeCohortID": 4,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 3,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-07-13T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 25954564,
"EncounterRecordNumber": "1025954564",
"PhysicianID": 842,
"AgeCohortID": 3,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 2,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-08-04T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 25971328,
"EncounterRecordNumber": "1025971328",
"PhysicianID": 3343,
"AgeCohortID": 3,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 2,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-07-25T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 27006329,
"EncounterRecordNumber": "1027006329",
"PhysicianID": 4269,
"AgeCohortID": 3,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 2,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-10-01T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 29625457,
"EncounterRecordNumber": "1029625457",
"PhysicianID": 842,
"AgeCohortID": 4,
"EntityID": 2,
"ServiceLineID": 13,
"LengthOfStay": 9,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-11-24T00:00:00Z",
"IsExcluded": false
},
{
"EncounterId": 30059663,
"EncounterRecordNumber": "1030059663",
"PhysicianID": 24764,
"AgeCohortID": 4,
"EntityID": 13,
"ServiceLineID": 13,
"LengthOfStay": 6,
"IsQVIEncounter": false,
"DischargeDateTime": "2023-11-19T00:00:00Z",
"IsExcluded": false
}
]

Some files were not shown because too many files have changed in this diff Show More