using Cleanuparr.Infrastructure.Health; using Cleanuparr.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; using NSubstitute; using Shouldly; using Xunit; using HealthCheckStatus = Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus; namespace Cleanuparr.Infrastructure.Tests.Health; /// /// Basic tests for DatabaseHealthCheck. /// Note: Full integration testing requires a real database since in-memory provider /// doesn't support migrations (GetPendingMigrationsAsync). /// public class DatabaseHealthCheckTests : IDisposable { private readonly ILogger _logger; private DataContext? _dataContext; public DatabaseHealthCheckTests() { _logger = Substitute.For>(); } public void Dispose() { _dataContext?.Dispose(); } #region Constructor Tests [Fact] public void Constructor_WithValidDependencies_CreatesInstance() { // Arrange var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; _dataContext = new DataContext(options); // Act var healthCheck = new DatabaseHealthCheck(_dataContext, _logger); // Assert healthCheck.ShouldNotBeNull(); } #endregion #region Exception Handling Tests [Fact] public async Task CheckHealthAsync_WhenDisposedContext_ReturnsUnhealthy() { // Arrange var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; var disposedContext = new DataContext(options); disposedContext.Dispose(); var healthCheck = new DatabaseHealthCheck(disposedContext, _logger); // Act var result = await healthCheck.CheckHealthAsync(null!); // Assert result.Status.ShouldBe(HealthCheckStatus.Unhealthy); } [Fact] public async Task CheckHealthAsync_WhenUnhealthy_LogsError() { // Arrange var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; var disposedContext = new DataContext(options); disposedContext.Dispose(); var healthCheck = new DatabaseHealthCheck(disposedContext, _logger); // Act await healthCheck.CheckHealthAsync(null!); // Assert var errorCalls = _logger.ReceivedCalls() .Where(c => c.GetMethodInfo().Name == "Log") .Where(c => c.GetArguments().Length > 0 && c.GetArguments()[0] is LogLevel l && l == LogLevel.Error) .ToList(); errorCalls.ShouldNotBeEmpty(); } [Fact] public async Task CheckHealthAsync_WhenUnhealthy_DescriptionIndicatesFailure() { // Arrange var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; var disposedContext = new DataContext(options); disposedContext.Dispose(); var healthCheck = new DatabaseHealthCheck(disposedContext, _logger); // Act var result = await healthCheck.CheckHealthAsync(null!); // Assert result.Description.ShouldContain("failed", Case.Insensitive); } #endregion }