Template
248 lines
10 KiB
C#
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}");
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|