From d2054dda2982699b51b3204fe8dfb81c9aed870e Mon Sep 17 00:00:00 2001 From: Rhys Ickeringill Date: Tue, 17 Mar 2026 18:55:29 +1100 Subject: [PATCH] Add happy-path tests --- .../GlobalSetup.cs | 5 + .../HostFixture.cs | 73 +++++++++++ .../MultipleSettingTests.cs | 120 ++++++++++++++++++ ...ntityFrameworkCore.PostgreSQL.Tests.csproj | 23 ++++ .../README.md | 19 +++ .../SingleSettingTests.cs | 112 ++++++++++++++++ .../GlobalSetup.cs | 5 + .../HostFixture.cs | 75 +++++++++++ .../MultipleSettingTests.cs | 114 +++++++++++++++++ ...EntityFrameworkCore.SqlServer.Tests.csproj | 23 ++++ .../README.md | 18 +++ .../SingleSettingTests.cs | 106 ++++++++++++++++ 12 files changed, 693 insertions(+) create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/GlobalSetup.cs create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/HostFixture.cs create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/MultipleSettingTests.cs create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests.csproj create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/README.md create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/SingleSettingTests.cs create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/GlobalSetup.cs create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/HostFixture.cs create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/MultipleSettingTests.cs create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests.csproj create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/README.md create mode 100644 RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/SingleSettingTests.cs diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/GlobalSetup.cs b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/GlobalSetup.cs new file mode 100644 index 0000000..0be755e --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/GlobalSetup.cs @@ -0,0 +1,5 @@ +// Here you could define global logic that would affect all tests + +// You can use attributes at the assembly level to apply to all tests in the assembly +[assembly: System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +[assembly: NotInParallel] \ No newline at end of file diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/HostFixture.cs b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/HostFixture.cs new file mode 100644 index 0000000..2991d38 --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/HostFixture.cs @@ -0,0 +1,73 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using RAIC.Extensions.Configuration.EntityFrameworkCore.Extensions; +using RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Extensions; +using TUnit.Core.Interfaces; + +namespace RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests; + + +[Table("settings")] +public record Setting : ISetting +{ + [Key] + [Column("key")] + public required string Key { get; set; } + + [Required] + [Column("value")] + public required string Value { get; set; } +} + +public class FixtureDbContext(DbContextOptions options) : DbContext(options), ISettingsDbContext>, ISettingsDbContextFactory +{ + public DbSet Settings { get; set; } + + public static FixtureDbContext Create(DbContextOptions options) => new(options); +} + +public record FixtureOptions +{ + public required string SomeOption { get; set; } +} + + +public class HostFixture : IAsyncInitializer, IServiceScopeFactory +{ + + private IHost? _host; + + public IServiceScope CreateScope() => _host!.Services.CreateScope(); + + public async Task InitializeAsync() + { + var builder = Host.CreateApplicationBuilder(); + + // build a initial configuration + builder.Configuration + .AddInMemoryCollection(new Dictionary() { { nameof(FixtureOptions.SomeOption), "Memory value" }, { "ChannelName", "setting_channel" } }) + .AddUserSecrets() // for connection string + .AddEnvironmentVariables(prefix: "ASPNETCORE_"); + + // add DbContext based configuration using connection string sourced from initial config + builder.Configuration.AddDbContext(dbContextOptions => dbContextOptions.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); + + builder.Services + .AddDbContext(opt => opt.UseNpgsql(builder.Configuration.GetConnectionString("Default"))) + .AddPostgreSQLNotificationConfigurationReloadService(opts => opts.Bind(builder.Configuration)) + .AddOptions().Bind(builder.Configuration); + + builder.Logging.AddDebug(); + + _host = builder.Build(); + + await _host.StartAsync(); + + _ = _host.WaitForShutdownAsync(); + } +} diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/MultipleSettingTests.cs b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/MultipleSettingTests.cs new file mode 100644 index 0000000..a4e021e --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/MultipleSettingTests.cs @@ -0,0 +1,120 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests; + + +/// +/// Tests where multiple settings get changed in a single transaction +/// +[ClassDataSource(Shared = SharedType.PerClass)] +public class MultipleSettingTests +{ + private readonly IServiceScopeFactory _hostScopeFactory; + + public MultipleSettingTests(HostFixture hostFixture) + { + _hostScopeFactory = hostFixture; + } + + [Test] + public async Task AddSettingsTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Memory value"); + } + + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + context.Settings.AddRange( + new() { Key = "BeforeOption", Value = "Foo" }, + new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value" }, + new() { Key = "AfterOption", Value = "Bar" } + ); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(2)); // wait for longer than debounce interval before checking again + + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value"); + } + } + + [Test] + [DependsOn(nameof(AddSettingsTest))] + public async Task UpdateSettingsTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value"); + } + + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + context.Settings.UpdateRange( + new() { Key = "BeforeOption", Value = "Foobar" }, + new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value updated" }, + new() { Key = "AfterOption", Value = "Baz" } + ); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(2)); // wait for longer than debounce interval before checking again + + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value updated"); + } + } + + [Test] + [DependsOn(nameof(UpdateSettingsTest))] + public async Task RemoveSettingsTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value updated"); + } + + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + await context.Settings.Where(s => s.Key.EndsWith("Option")).ExecuteDeleteAsync(); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(2)); // wait for longer than debounce interval before checking again + + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Memory value"); + } + } +} diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests.csproj b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests.csproj new file mode 100644 index 0000000..56c13cb --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests.csproj @@ -0,0 +1,23 @@ + + + + enable + enable + Exe + net8.0 + 838c8b7c-8871-4029-8993-b595cd7aa32c + + + + + + + + + + + + + + + diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/README.md b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/README.md new file mode 100644 index 0000000..17c1524 --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/README.md @@ -0,0 +1,19 @@ +# RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests + +Some basic happy path only tests for functionality exposed by `RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.PostgreSQLNotificationConfigurationReloader` + +## Requirements + +Developers are expected to add their own User Secrets to the test project, containing a default connection string setting +(ie. with key `ConnectionStrings:Default`) that points to a pre-configured database that contains a `settings` table with columns +`key` & `value`. + +Additionally, the required insert, delete and update triggers (**both** of them!) must have been +added and enabled on the `settings` table otherwise tests will fail +(see the [RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL README](../RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL/README.md) +for suggestions of how to do this). + +## TODO +* Get EF to drop and create the database itself (using `EnsureDeleted()` & `EnsureCreated()`) instead of +forcing this on the user +* Additional tests off the happy path \ No newline at end of file diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/SingleSettingTests.cs b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/SingleSettingTests.cs new file mode 100644 index 0000000..ee32739 --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests/SingleSettingTests.cs @@ -0,0 +1,112 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace RAIC.Extensions.Configuration.EntityFrameworkCore.PostgreSQL.Tests; + + +/// +/// Happy path tests +/// +[ClassDataSource(Shared = SharedType.PerClass)] +public class SingleSettingTests +{ + private readonly IServiceScopeFactory _hostScopeFactory; + + public SingleSettingTests(HostFixture hostFixture) + { + _hostScopeFactory = hostFixture; + } + + [Test] + public async Task AddSettingTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Memory value"); + } + + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + context.Settings.Add(new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value" }); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(2)); // wait for longer than debounce interval before checking again + + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value"); + } + } + + [Test] + [DependsOn(nameof(AddSettingTest))] + public async Task UpdateSettingTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value"); + } + + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + context.Settings.Update(new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value updated" }); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(2)); // wait for longer than debounce interval before checking again + + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value updated"); + } + } + + [Test] + [DependsOn(nameof(UpdateSettingTest))] + public async Task RemoveSettingTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value updated"); + } + + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + await context.Settings.Where(s => s.Key == nameof(FixtureOptions.SomeOption)).ExecuteDeleteAsync(); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(2)); // wait for longer than debounce interval before checking again + + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Memory value"); + } + } +} diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/GlobalSetup.cs b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/GlobalSetup.cs new file mode 100644 index 0000000..0be755e --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/GlobalSetup.cs @@ -0,0 +1,5 @@ +// Here you could define global logic that would affect all tests + +// You can use attributes at the assembly level to apply to all tests in the assembly +[assembly: System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +[assembly: NotInParallel] \ No newline at end of file diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/HostFixture.cs b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/HostFixture.cs new file mode 100644 index 0000000..6243cb6 --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/HostFixture.cs @@ -0,0 +1,75 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using RAIC.Extensions.Configuration.EntityFrameworkCore.Extensions; +using RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Extensions; +using TUnit.Core.Interfaces; + +namespace RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests; + + +[Table("Settings", Schema = "dbo")] +public record Setting : ISetting +{ + [Key] + public required string Key { get; set; } + + [Required] + public required string Value { get; set; } +} + +public class FixtureDbContext(DbContextOptions options) : DbContext(options), ISettingsDbContext>, ISettingsDbContextFactory +{ + public DbSet Settings { get; set; } + + public static FixtureDbContext Create(DbContextOptions options) => new(options); +} + +public class FixtureOptions +{ + public required string SomeOption { get; set; } +} + + +public class HostFixture: IAsyncInitializer, IServiceScopeFactory +{ + + private IHost? _host; + + public IServiceScope CreateScope() => _host!.Services.CreateScope(); + + public async Task InitializeAsync() + { + var builder = Host.CreateApplicationBuilder(); + + // build a initial configuration + builder.Configuration + .AddInMemoryCollection(new Dictionary() + { + { nameof(FixtureOptions.SomeOption), "Memory value" }, + }) + .AddUserSecrets() // for connection string + .AddEnvironmentVariables(prefix: "ASPNETCORE_"); + + // add DbContext based configuration using connection string sourced from initial config + builder.Configuration.AddDbContext(dbContextOptions => dbContextOptions.UseSqlServer(builder.Configuration.GetConnectionString("Default"))); + + builder.Services + .AddDbContext(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("Default"))) + .AddSqlServerNotificationConfigurationReloadService() + .AddOptions().Bind(builder.Configuration); + + builder.Logging.AddDebug(); + + _host = builder.Build(); + + await _host.StartAsync(); + + _ = _host.WaitForShutdownAsync(); + } +} diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/MultipleSettingTests.cs b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/MultipleSettingTests.cs new file mode 100644 index 0000000..f42f7bc --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/MultipleSettingTests.cs @@ -0,0 +1,114 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests; + + +/// +/// Tests where multiple settings get changed in a single transaction +/// +[ClassDataSource(Shared = SharedType.PerClass)] +public class MultipleSettingTests +{ + private readonly IServiceScopeFactory _hostScopeFactory; + + public MultipleSettingTests(HostFixture hostFixture) + { + _hostScopeFactory = hostFixture; + } + + [Test] + public async Task AddSettingsTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Memory value"); + } + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + context.Settings.AddRange( + new() { Key = "BeforeOption", Value = "Foo" }, + new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value" }, + new() { Key = "AfterOption", Value = "Bar" } + ); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value"); + } + } + + [Test] + [DependsOn(nameof(AddSettingsTest))] + public async Task UpdateSettingsTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value"); + } + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + context.Settings.UpdateRange( + new() { Key = "BeforeOption", Value = "Foobar" }, + new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value updated" }, + new() { Key = "AfterOption", Value = "Baz" } + ); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value updated"); + } + } + + [Test] + [DependsOn(nameof(UpdateSettingsTest))] + public async Task RemoveSettingsTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value updated"); + } + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + await context.Settings.Where(s => s.Key.EndsWith("Option")).ExecuteDeleteAsync(); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Memory value"); + } + } +} diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests.csproj b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests.csproj new file mode 100644 index 0000000..418f7e7 --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests.csproj @@ -0,0 +1,23 @@ + + + + enable + enable + Exe + net8.0 + cecca246-e624-4ad1-b5a4-7d7b915dfd10 + + + + + + + + + + + + + + + diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/README.md b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/README.md new file mode 100644 index 0000000..b267fe1 --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/README.md @@ -0,0 +1,18 @@ +# RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests + +Some basic happy path only tests for functionality exposed by `RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.SqlServerNotificationConfigurationReloader` + +## Requirements + +Developers are expected to add their own User Secrets to the test project, containing a default connection string setting +(ie. with key `ConnectionStrings:Default`) that points to a pre-configured database that contains a `Settings` table with columns +`key` & `value`. + +Additionally, **both** the database and `Settings` table must have change tracking enabled otherwise tests will fail +(see the [RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer README](../RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer/README.md) for details of how to +do this). + +## TODO +* Get EF to drop and create the database itself (using `EnsureDeleted()` & `EnsureCreated()`) instead of +forcing this on the user +* Additional tests off the happy path \ No newline at end of file diff --git a/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/SingleSettingTests.cs b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/SingleSettingTests.cs new file mode 100644 index 0000000..f83a1e0 --- /dev/null +++ b/RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests/SingleSettingTests.cs @@ -0,0 +1,106 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace RAIC.Extensions.Configuration.EntityFrameworkCore.SqlServer.Tests; + + +/// +/// Happy path tests +/// +[ClassDataSource(Shared = SharedType.PerClass)] +public class SingleSettingTests +{ + private readonly IServiceScopeFactory _hostScopeFactory; + + public SingleSettingTests(HostFixture hostFixture) + { + _hostScopeFactory = hostFixture; + } + + [Test] + public async Task AddSettingTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Memory value"); + } + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + context.Settings.Add(new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value" }); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value"); + } + } + + [Test] + [DependsOn(nameof(AddSettingTest))] + public async Task UpdateSettingTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value"); + } + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + context.Settings.Update(new() { Key = nameof(FixtureOptions.SomeOption), Value = "Db value updated" }); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Db value updated"); + } + } + + [Test] + [DependsOn(nameof(UpdateSettingTest))] + public async Task RemoveSettingTest() + { + // Pre-condition sanity check! + using (var preconditionScope = _hostScopeFactory.CreateScope()) + { + var initialOptionsState = preconditionScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(initialOptionsState.SomeOption).IsEqualTo("Db value updated"); + } + + // Act + using (var actScope = _hostScopeFactory.CreateScope()) + { + var context = actScope.ServiceProvider.GetRequiredService(); + await context.Settings.Where(s => s.Key == nameof(FixtureOptions.SomeOption)).ExecuteDeleteAsync(); + await context.SaveChangesAsync(); + } + + await Task.Delay(TimeSpan.FromSeconds(0.333)); // wait a bit to give config time to settle + + // Assert + using (var assertScope = _hostScopeFactory.CreateScope()) + { + var finalOptionsState = assertScope.ServiceProvider.GetRequiredService>().Value; + await Assert.That(finalOptionsState.SomeOption).IsEqualTo("Memory value"); + } + } +}