From cd349dc1a24db953dbddc60e0cffb47b6869681d Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Tue, 25 Aug 2026 10:43:07 +0100 Subject: [PATCH 1/2] Fix estate-specific resolver connection string --- .../DbContextResolverXTests.cs | 40 +++++++++ ...eportingAPI.BusinessLogic.UnitTests.csproj | 4 +- .../UnitTest1.cs | 84 ------------------- .../QueryTimingInterceptor.cs | 4 +- 4 files changed, 44 insertions(+), 88 deletions(-) create mode 100644 EstateReportingAPI.BusinessLogic.UnitTests/DbContextResolverXTests.cs delete mode 100644 EstateReportingAPI.BusinessLogic.UnitTests/UnitTest1.cs diff --git a/EstateReportingAPI.BusinessLogic.UnitTests/DbContextResolverXTests.cs b/EstateReportingAPI.BusinessLogic.UnitTests/DbContextResolverXTests.cs new file mode 100644 index 0000000..852c7c7 --- /dev/null +++ b/EstateReportingAPI.BusinessLogic.UnitTests/DbContextResolverXTests.cs @@ -0,0 +1,40 @@ +using EstateReportingAPI.BusinessLogic; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Shared.EntityFramework; +using Shared.General; +using Shouldly; +using TransactionProcessor.Database.Contexts; + +namespace EstateReportingAPI.BusinessLogic.UnitTests; + +public class DbContextResolverXTests +{ + [Fact] + public void Resolve_WhenEstateSuffixIsProvided_UsesTheSuffixedCatalog() + { + IConfigurationRoot configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["ConnectionStrings:TransactionProcessorReadModel"] = "Server=localhost;Initial Catalog=TransactionProcessorReadModel;Integrated Security=True;TrustServerCertificate=True" + }) + .Build(); + + ConfigurationReader.Initialise(configuration); + + using ServiceProvider rootProvider = new ServiceCollection().BuildServiceProvider(); + DbContextResolverX resolver = + new(rootProvider, configuration, new QueryTimingInterceptor()); + + using ResolvedDbContext resolvedContext = + resolver.Resolve("TransactionProcessorReadModel", "estate-123"); + + SqlConnectionStringBuilder connectionStringBuilder = + new(resolvedContext.Context.Database.GetDbConnection().ConnectionString); + + connectionStringBuilder.InitialCatalog.ShouldBe("TransactionProcessorReadModel-estate-123"); + } +} diff --git a/EstateReportingAPI.BusinessLogic.UnitTests/EstateReportingAPI.BusinessLogic.UnitTests.csproj b/EstateReportingAPI.BusinessLogic.UnitTests/EstateReportingAPI.BusinessLogic.UnitTests.csproj index 5f04f9e..e6b38ec 100644 --- a/EstateReportingAPI.BusinessLogic.UnitTests/EstateReportingAPI.BusinessLogic.UnitTests.csproj +++ b/EstateReportingAPI.BusinessLogic.UnitTests/EstateReportingAPI.BusinessLogic.UnitTests.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable enable @@ -18,7 +18,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/EstateReportingAPI.BusinessLogic.UnitTests/UnitTest1.cs b/EstateReportingAPI.BusinessLogic.UnitTests/UnitTest1.cs deleted file mode 100644 index 414d79d..0000000 --- a/EstateReportingAPI.BusinessLogic.UnitTests/UnitTest1.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace EstateReportingAPI.BusinessLogic.UnitTests -{ - using EstateManagement.Database.Contexts; - using EstateManagement.Database.Entities; - using Microsoft.EntityFrameworkCore; - using Microsoft.EntityFrameworkCore.Infrastructure; - using Moq; - using Shouldly; - - public class ReportingManagerTests - { - //Task> GetCalendarComparisonDates(Guid estateId, CancellationToken cancellationToken); - //Task> GetCalendarDates(Guid estateId, CancellationToken cancellationToken); - //Task> GetCalendarYears(Guid estateId, CancellationToken cancellationToken); - //Task GetMerchantsTransactionKpis(Guid estateId, CancellationToken cancellationToken); - //Task GetTodaysFailedSales(Guid estateId, DateTime comparisonDate, String responseCode, CancellationToken cancellationToken); - //Task GetTodaysSales(Guid estateId, DateTime comparisonDate, CancellationToken cancellationToken); - //Task> GetTodaysSalesCountByHour(Guid estateId, DateTime comparisonDate, CancellationToken cancellationToken); - //Task> GetTodaysSalesValueByHour(Guid estateId, DateTime comparisonDate, CancellationToken cancellationToken); - //Task GetTodaysSettlement(Guid estateId, DateTime comparisonDate, CancellationToken cancellationToken); - //Task> GetTopBottomData(Guid estateId, TopBottom direction, Int32 resultCount, Dimension dimension, CancellationToken cancellationToken); - - public class TestData{ - public static Guid EstateId => Guid.Parse("F64241E7-F778-4F77-8A64-099CB51BF4CE"); - } - - [Fact] - public async Task ReportingManager_GetCalendarYears_YearsAreReturned(){ - - //Required properties '{'DayOfWeek', 'DayOfWeekShort', 'MonthNameLong', 'MonthNameShort', 'WeekNumberString', 'YearWeekNumber'}' - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: "TestDatabase") - .Options; - - EstateManagementGenericContext context = new EstateManagementSqlServerContext(options); - await context.Database.EnsureCreatedAsync(); - - await context.Calendar.AddRangeAsync(new Calendar{ - Date = new DateTime(2023, 1, 1), - DayOfWeek = "Monday", - DayOfWeekShort = "Mon", - MonthNameLong = "January", - MonthNameShort ="Jan", - WeekNumberString = "01", - YearWeekNumber = "202301", - Year = 2023 - }, - new Calendar - { - Date = new DateTime(2022, 1, 1), - DayOfWeek = "Monday", - DayOfWeekShort = "Mon", - MonthNameLong = "January", - MonthNameShort = "Jan", - WeekNumberString = "01", - YearWeekNumber = "202301", - Year = 2022 - }, - new Calendar - { - Date = new DateTime(2021, 1, 1), - DayOfWeek = "Monday", - DayOfWeekShort = "Mon", - MonthNameLong = "January", - MonthNameShort = "Jan", - WeekNumberString = "01", - YearWeekNumber = "202101", - Year = 2021 - }); - await context.SaveChangesAsync(); - Mock> resolver = new Mock>(); - resolver.Setup(r => r.GetContext(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(context); - - var manager = new ReportingManager(resolver.Object); - - var years = await manager.GetCalendarYears(TestData.EstateId, CancellationToken.None); - - years.Count.ShouldBe(3); - years.ShouldContain(2021); - years.ShouldContain(2022); - years.ShouldContain(2023); - } - } -} \ No newline at end of file diff --git a/EstateReportingAPI.BusinessLogic/QueryTimingInterceptor.cs b/EstateReportingAPI.BusinessLogic/QueryTimingInterceptor.cs index 950e576..cdbb0ff 100644 --- a/EstateReportingAPI.BusinessLogic/QueryTimingInterceptor.cs +++ b/EstateReportingAPI.BusinessLogic/QueryTimingInterceptor.cs @@ -98,7 +98,7 @@ public ResolvedDbContext Resolve(String connectionStringKey, if (retryOptions != null) { services.AddDbContext(options => { - options.UseSharedSqlServer(ConfigurationReader.GetConnectionString("TransactionProcessorReadModel"), retry => { + options.UseSharedSqlServer(connectionString, retry => { retry.AdditionalTransientErrorNumbers = retryOptions.AdditionalTransientErrorNumbers; retry.MaxRetryCount = retryOptions.MaxRetryCount; retry.MaxRetryDelay = retryOptions.MaxRetryDelay; @@ -109,7 +109,7 @@ public ResolvedDbContext Resolve(String connectionStringKey, else { services.AddDbContext(options => { - options.UseSqlServer(ConfigurationReader.GetConnectionString("TransactionProcessorReadModel"), retry => { retry.EnableRetryOnFailure(); }); + options.UseSqlServer(connectionString, retry => { retry.EnableRetryOnFailure(); }); options.AddInterceptors(Interceptor); }); From c8eb64cd531a187fd03385e84f3db64d9fce058e Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Tue, 25 Aug 2026 11:18:09 +0100 Subject: [PATCH 2/2] Move resolver regression into CI test project --- ...eportingAPI.BusinessLogic.UnitTests.csproj | 4 +- .../UnitTest1.cs | 84 +++++++++++++++++++ .../BusinessLogic}/DbContextResolverXTests.cs | 6 +- .../EstateReportingAPI.Tests.csproj | 1 + 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 EstateReportingAPI.BusinessLogic.UnitTests/UnitTest1.cs rename {EstateReportingAPI.BusinessLogic.UnitTests => EstateReportingAPI.Tests/BusinessLogic}/DbContextResolverXTests.cs (94%) diff --git a/EstateReportingAPI.BusinessLogic.UnitTests/EstateReportingAPI.BusinessLogic.UnitTests.csproj b/EstateReportingAPI.BusinessLogic.UnitTests/EstateReportingAPI.BusinessLogic.UnitTests.csproj index e6b38ec..5f04f9e 100644 --- a/EstateReportingAPI.BusinessLogic.UnitTests/EstateReportingAPI.BusinessLogic.UnitTests.csproj +++ b/EstateReportingAPI.BusinessLogic.UnitTests/EstateReportingAPI.BusinessLogic.UnitTests.csproj @@ -1,7 +1,7 @@ - net10.0 + net7.0 enable enable @@ -18,7 +18,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/EstateReportingAPI.BusinessLogic.UnitTests/UnitTest1.cs b/EstateReportingAPI.BusinessLogic.UnitTests/UnitTest1.cs new file mode 100644 index 0000000..25541a0 --- /dev/null +++ b/EstateReportingAPI.BusinessLogic.UnitTests/UnitTest1.cs @@ -0,0 +1,84 @@ +namespace EstateReportingAPI.BusinessLogic.UnitTests +{ + using EstateManagement.Database.Contexts; + using EstateManagement.Database.Entities; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Infrastructure; + using Moq; + using Shouldly; + + public class ReportingManagerTests + { + //Task> GetCalendarComparisonDates(Guid estateId, CancellationToken cancellationToken); + //Task> GetCalendarDates(Guid estateId, CancellationToken cancellationToken); + //Task> GetCalendarYears(Guid estateId, CancellationToken cancellationToken); + //Task GetMerchantsTransactionKpis(Guid estateId, CancellationToken cancellationToken); + //Task GetTodaysFailedSales(Guid estateId, DateTime comparisonDate, String responseCode, CancellationToken cancellationToken); + //Task GetTodaysSales(Guid estateId, DateTime comparisonDate, CancellationToken cancellationToken); + //Task> GetTodaysSalesCountByHour(Guid estateId, DateTime comparisonDate, CancellationToken cancellationToken); + //Task> GetTodaysSalesValueByHour(Guid estateId, DateTime comparisonDate, CancellationToken cancellationToken); + //Task GetTodaysSettlement(Guid estateId, DateTime comparisonDate, CancellationToken cancellationToken); + //Task> GetTopBottomData(Guid estateId, TopBottom direction, Int32 resultCount, Dimension dimension, CancellationToken cancellationToken); + + public class TestData{ + public static Guid EstateId => Guid.Parse("F64241E7-F778-4F77-8A64-099CB51BF4CE"); + } + + [Fact] + public async Task ReportingManager_GetCalendarYears_YearsAreReturned(){ + + //Required properties '{'DayOfWeek', 'DayOfWeekShort', 'MonthNameLong', 'MonthNameShort', 'WeekNumberString', 'YearWeekNumber'}' + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: "TestDatabase") + .Options; + + EstateManagementGenericContext context = new EstateManagementSqlServerContext(options); + await context.Database.EnsureCreatedAsync(); + + await context.Calendar.AddRangeAsync(new Calendar{ + Date = new DateTime(2023, 1, 1), + DayOfWeek = "Monday", + DayOfWeekShort = "Mon", + MonthNameLong = "January", + MonthNameShort ="Jan", + WeekNumberString = "01", + YearWeekNumber = "202301", + Year = 2023 + }, + new Calendar + { + Date = new DateTime(2022, 1, 1), + DayOfWeek = "Monday", + DayOfWeekShort = "Mon", + MonthNameLong = "January", + MonthNameShort = "Jan", + WeekNumberString = "01", + YearWeekNumber = "202301", + Year = 2022 + }, + new Calendar + { + Date = new DateTime(2021, 1, 1), + DayOfWeek = "Monday", + DayOfWeekShort = "Mon", + MonthNameLong = "January", + MonthNameShort = "Jan", + WeekNumberString = "01", + YearWeekNumber = "202101", + Year = 2021 + }); + await context.SaveChangesAsync(); + Mock> resolver = new Mock>(); + resolver.Setup(r => r.GetContext(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(context); + + var manager = new ReportingManager(resolver.Object); + + var years = await manager.GetCalendarYears(TestData.EstateId, CancellationToken.None); + + years.Count.ShouldBe(3); + years.ShouldContain(2021); + years.ShouldContain(2022); + years.ShouldContain(2023); + } + } +} diff --git a/EstateReportingAPI.BusinessLogic.UnitTests/DbContextResolverXTests.cs b/EstateReportingAPI.Tests/BusinessLogic/DbContextResolverXTests.cs similarity index 94% rename from EstateReportingAPI.BusinessLogic.UnitTests/DbContextResolverXTests.cs rename to EstateReportingAPI.Tests/BusinessLogic/DbContextResolverXTests.cs index 852c7c7..f6a9fa2 100644 --- a/EstateReportingAPI.BusinessLogic.UnitTests/DbContextResolverXTests.cs +++ b/EstateReportingAPI.Tests/BusinessLogic/DbContextResolverXTests.cs @@ -4,12 +4,14 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; using Shared.EntityFramework; using Shared.General; using Shouldly; using TransactionProcessor.Database.Contexts; +using Xunit; -namespace EstateReportingAPI.BusinessLogic.UnitTests; +namespace EstateReportingAPI.Tests.BusinessLogic; public class DbContextResolverXTests { @@ -17,7 +19,7 @@ public class DbContextResolverXTests public void Resolve_WhenEstateSuffixIsProvided_UsesTheSuffixedCatalog() { IConfigurationRoot configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new Dictionary + .AddInMemoryCollection(new Dictionary { ["ConnectionStrings:TransactionProcessorReadModel"] = "Server=localhost;Initial Catalog=TransactionProcessorReadModel;Integrated Security=True;TrustServerCertificate=True" }) diff --git a/EstateReportingAPI.Tests/EstateReportingAPI.Tests.csproj b/EstateReportingAPI.Tests/EstateReportingAPI.Tests.csproj index aec0648..9fd8de3 100644 --- a/EstateReportingAPI.Tests/EstateReportingAPI.Tests.csproj +++ b/EstateReportingAPI.Tests/EstateReportingAPI.Tests.csproj @@ -35,6 +35,7 @@ +