using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using ZB.MOM.WW.Health.EntityFrameworkCore; namespace ZB.MOM.WW.Health.EntityFrameworkCore.Tests; /// /// Verifies against a real SQLite database (in-memory, /// connection kept open) so the CanConnectAsync semantics exercise an actual provider: /// reachable → Healthy, unopenable connection → Unhealthy (no throw escapes), a custom /// that queries → Healthy, and a /// throwing ProbeQuery → Unhealthy. Both the and /// the scoped-TContext resolution paths are covered. /// public sealed class DatabaseHealthCheckTests { /// A minimal context with one entity, used purely to drive provider behaviour. private sealed class WidgetContext : DbContext { public WidgetContext(DbContextOptions options) : base(options) { } public DbSet Widgets => Set(); } private sealed class Widget { public int Id { get; set; } } private static HealthCheckContext NewContext() => new() { Registration = new HealthCheckRegistration( "database", sp => throw new InvalidOperationException("not used"), HealthStatus.Unhealthy, tags: null), }; /// /// Builds a provider whose is backed by the supplied open /// SQLite connection (and creates the schema). When is true the /// context is registered via AddDbContextFactory; otherwise via AddDbContext (scoped). /// private static IServiceProvider BuildProvider(SqliteConnection connection, bool useFactory) { connection.Open(); var services = new ServiceCollection(); if (useFactory) { services.AddDbContextFactory(o => o.UseSqlite(connection)); } else { services.AddDbContext(o => o.UseSqlite(connection)); } var provider = services.BuildServiceProvider(); using var scope = provider.CreateScope(); scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); return provider; } [Theory] [InlineData(true)] [InlineData(false)] public async Task ReachableContext_Healthy(bool useFactory) { using var connection = new SqliteConnection("DataSource=:memory:"); var provider = BuildProvider(connection, useFactory); var check = new DatabaseHealthCheck(provider); var result = await check.CheckHealthAsync(NewContext(), CancellationToken.None); Assert.Equal(HealthStatus.Healthy, result.Status); } [Fact] public async Task UnopenableConnection_Unhealthy_NoThrow() { // Point the context at a file path that cannot be opened (parent directory does not exist). var bogusPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"), "missing", "db.sqlite"); var services = new ServiceCollection(); services.AddDbContext(o => o.UseSqlite($"DataSource={bogusPath};Mode=ReadWrite")); var provider = services.BuildServiceProvider(); var check = new DatabaseHealthCheck(provider); var result = await check.CheckHealthAsync(NewContext(), CancellationToken.None); Assert.Equal(HealthStatus.Unhealthy, result.Status); } [Fact] public async Task CustomProbeQuery_RunsQuery_Healthy() { using var connection = new SqliteConnection("DataSource=:memory:"); var provider = BuildProvider(connection, useFactory: true); var options = new DatabaseHealthCheckOptions { ProbeQuery = (ctx, ct) => ctx.Widgets.AsNoTracking().AnyAsync(ct), }; var check = new DatabaseHealthCheck(provider, options); var result = await check.CheckHealthAsync(NewContext(), CancellationToken.None); Assert.Equal(HealthStatus.Healthy, result.Status); } [Fact] public async Task ProbeQueryThrows_Unhealthy() { using var connection = new SqliteConnection("DataSource=:memory:"); var provider = BuildProvider(connection, useFactory: false); var options = new DatabaseHealthCheckOptions { ProbeQuery = (_, _) => throw new InvalidOperationException("boom"), }; var check = new DatabaseHealthCheck(provider, options); var result = await check.CheckHealthAsync(NewContext(), CancellationToken.None); Assert.Equal(HealthStatus.Unhealthy, result.Status); } }