Files
continuousimprovement/tests/Strata.ContinuousImprovement.Biz.Test.Unit/DbContexts/JazzDbContextTests.cs
T

248 lines
10 KiB
C#

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