using ChatBot.Data; using ChatBot.Services; using ChatBot.Tests.TestUtilities; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; namespace ChatBot.Tests.Services; public class DatabaseInitializationServiceTests : UnitTestBase { [Fact] public void DatabaseInitializationService_ShouldCreateInstance() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); // Act var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Assert Assert.NotNull(service); } [Fact] public async Task DatabaseInitializationService_StopAsync_ShouldComplete() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Act & Assert await service.StopAsync(CancellationToken.None); // If we reach here, the method completed successfully Assert.True(true); } [Fact] public async Task StartAsync_ShouldLogCorrectInformation_WhenStopping() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Act await service.StopAsync(CancellationToken.None); // Assert loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("Database initialization service stopped") ), It.IsAny(), It.IsAny>() ), Times.Once ); } [Fact] public async Task StartAsync_ShouldHandleCancellationToken() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); var cts = new CancellationTokenSource(); cts.Cancel(); // Cancel immediately // Setup service provider to throw when CreateScope is called serviceProviderMock .Setup(x => x.GetService(typeof(IServiceScopeFactory))) .Returns((IServiceScopeFactory)null!); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Act & Assert var act = async () => await service.StartAsync(cts.Token); await act.Should().ThrowAsync(); } [Fact] public async Task StartAsync_ShouldLogStartingMessage() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); // Setup service provider to throw when CreateScope is called serviceProviderMock .Setup(x => x.GetService(typeof(IServiceScopeFactory))) .Returns((IServiceScopeFactory)null!); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Act & Assert var act = async () => await service.StartAsync(CancellationToken.None); await act.Should().ThrowAsync(); loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("Starting database initialization...") ), It.IsAny(), It.IsAny>() ), Times.Once ); } [Fact] public async Task StartAsync_ShouldThrowExceptionWhenServiceProviderFails() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); // Setup service provider to throw when CreateScope is called serviceProviderMock .Setup(x => x.GetService(typeof(IServiceScopeFactory))) .Returns((IServiceScopeFactory)null!); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Act & Assert var act = async () => await service.StartAsync(CancellationToken.None); await act.Should().ThrowAsync(); // Verify that starting message was logged loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("Starting database initialization...") ), It.IsAny(), It.IsAny>() ), Times.Once ); } [Fact] public async Task StartAsync_ShouldHandleOperationCanceledException() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); var cts = new CancellationTokenSource(); cts.Cancel(); // Cancel immediately // Setup service provider to throw when CreateScope is called serviceProviderMock .Setup(x => x.GetService(typeof(IServiceScopeFactory))) .Returns((IServiceScopeFactory)null!); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Act & Assert var act = async () => await service.StartAsync(cts.Token); await act.Should().ThrowAsync(); } [Fact] public async Task StartAsync_ShouldHandleGeneralException() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); // Setup service provider to throw when CreateScope is called serviceProviderMock .Setup(x => x.GetService(typeof(IServiceScopeFactory))) .Returns((IServiceScopeFactory)null!); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Act & Assert var act = async () => await service.StartAsync(CancellationToken.None); await act.Should().ThrowAsync(); } [Fact] public async Task StartAsync_ShouldThrowExceptionWithServiceProviderError() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); // Setup service provider to throw when CreateScope is called serviceProviderMock .Setup(x => x.GetService(typeof(IServiceScopeFactory))) .Returns((IServiceScopeFactory)null!); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Act & Assert var act = async () => await service.StartAsync(CancellationToken.None); await act.Should().ThrowAsync(); // Verify that starting message was logged loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("Starting database initialization...") ), It.IsAny(), It.IsAny>() ), Times.Once ); } [Fact] public async Task StartAsync_WhenDatabaseExists_ShouldLogAndMigrate() { // Arrange var dbPath = $"TestDb_{Guid.NewGuid()}.db"; var services = new ServiceCollection(); services.AddDbContext(options => options.UseSqlite($"Data Source={dbPath}") .ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)) ); var serviceProvider = services.BuildServiceProvider(); var loggerMock = new Mock>(); var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object); try { // Act await service.StartAsync(CancellationToken.None); // Assert loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("Starting database initialization") ), It.IsAny(), It.IsAny>() ), Times.Once ); loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains("Database initialization completed successfully") ), It.IsAny(), It.IsAny>() ), Times.Once ); } finally { // Cleanup serviceProvider.Dispose(); GC.Collect(); GC.WaitForPendingFinalizers(); if (File.Exists(dbPath)) { try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ } } } } [Fact] public async Task StopAsync_WithCancellationToken_ShouldComplete() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); var cts = new CancellationTokenSource(); // Act await service.StopAsync(cts.Token); // Assert loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("Database initialization service stopped") ), It.IsAny(), It.IsAny>() ), Times.Once ); } [Fact] public async Task StopAsync_WhenCancellationRequested_ShouldStillComplete() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); var cts = new CancellationTokenSource(); cts.Cancel(); // Act await service.StopAsync(cts.Token); // Assert loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("Database initialization service stopped") ), It.IsAny(), It.IsAny>() ), Times.Once ); } [Fact] public async Task StartAsync_ShouldHandleDatabaseDoesNotExistException() { // Arrange var dbPath = $"TestDb_{Guid.NewGuid()}.db"; var services = new ServiceCollection(); services.AddDbContext(options => options.UseSqlite($"Data Source={dbPath}") .ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)) ); var serviceProvider = services.BuildServiceProvider(); var loggerMock = new Mock>(); var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object); try { // Act await service.StartAsync(CancellationToken.None); // Assert - service should complete successfully loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains("Database initialization completed successfully") ), It.IsAny(), It.IsAny>() ), Times.Once ); } finally { // Cleanup serviceProvider.Dispose(); GC.Collect(); GC.WaitForPendingFinalizers(); if (File.Exists(dbPath)) { try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ } } } } [Fact] public async Task StartAsync_WithValidDatabase_ShouldLogDatabaseExists() { // Arrange var dbPath = $"TestDb_{Guid.NewGuid()}.db"; var services = new ServiceCollection(); services.AddDbContext(options => options.UseSqlite($"Data Source={dbPath}") .ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)) ); var serviceProvider = services.BuildServiceProvider(); var loggerMock = new Mock>(); var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object); try { // Act await service.StartAsync(CancellationToken.None); // Assert loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is((v, t) => true), // Any log message It.IsAny(), It.IsAny>() ), Times.AtLeastOnce ); } finally { // Cleanup serviceProvider.Dispose(); GC.Collect(); GC.WaitForPendingFinalizers(); if (File.Exists(dbPath)) { try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ } } } } [Fact] public void DatabaseInitializationService_ShouldImplementIHostedService() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); // Act var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Assert service.Should().BeAssignableTo(); } [Fact] public async Task StartAsync_MultipleCallsInSequence_ShouldWork() { // Arrange var dbPath = $"TestDb_{Guid.NewGuid()}.db"; var services = new ServiceCollection(); services.AddDbContext(options => options.UseSqlite($"Data Source={dbPath}") .ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)) ); var serviceProvider = services.BuildServiceProvider(); var loggerMock = new Mock>(); var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object); try { // Act await service.StartAsync(CancellationToken.None); await service.StopAsync(CancellationToken.None); // Assert - should complete without exceptions loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()! .Contains("Database initialization completed successfully") ), It.IsAny(), It.IsAny>() ), Times.Once ); } finally { // Cleanup serviceProvider.Dispose(); GC.Collect(); GC.WaitForPendingFinalizers(); if (File.Exists(dbPath)) { try { File.Delete(dbPath); } catch { /* Ignore cleanup errors */ } } } } [Fact] public async Task StopAsync_WithoutStartAsync_ShouldComplete() { // Arrange var serviceProviderMock = new Mock(); var loggerMock = new Mock>(); var service = new DatabaseInitializationService( serviceProviderMock.Object, loggerMock.Object ); // Act await service.StopAsync(CancellationToken.None); // Assert - should complete without exceptions loggerMock.Verify( x => x.Log( LogLevel.Information, It.IsAny(), It.Is( (v, t) => v.ToString()!.Contains("Database initialization service stopped") ), It.IsAny(), It.IsAny>() ), Times.Once ); } }