Template
chore: deploy initial code base
This commit is contained in:
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user