583 lines
19 KiB
C#
583 lines
19 KiB
C#
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<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
|
|
// 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<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
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<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
var service = new DatabaseInitializationService(
|
|
serviceProviderMock.Object,
|
|
loggerMock.Object
|
|
);
|
|
|
|
// Act
|
|
await service.StopAsync(CancellationToken.None);
|
|
|
|
// Assert
|
|
loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Database initialization service stopped")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_ShouldHandleCancellationToken()
|
|
{
|
|
// Arrange
|
|
var serviceProviderMock = new Mock<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
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<InvalidOperationException>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_ShouldLogStartingMessage()
|
|
{
|
|
// Arrange
|
|
var serviceProviderMock = new Mock<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
|
|
// 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<InvalidOperationException>();
|
|
|
|
loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Starting database initialization...")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_ShouldThrowExceptionWhenServiceProviderFails()
|
|
{
|
|
// Arrange
|
|
var serviceProviderMock = new Mock<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
|
|
// 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<InvalidOperationException>();
|
|
|
|
// Verify that starting message was logged
|
|
loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Starting database initialization...")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_ShouldHandleOperationCanceledException()
|
|
{
|
|
// Arrange
|
|
var serviceProviderMock = new Mock<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
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<InvalidOperationException>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_ShouldHandleGeneralException()
|
|
{
|
|
// Arrange
|
|
var serviceProviderMock = new Mock<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
|
|
// 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<InvalidOperationException>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_ShouldThrowExceptionWithServiceProviderError()
|
|
{
|
|
// Arrange
|
|
var serviceProviderMock = new Mock<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
|
|
// 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<InvalidOperationException>();
|
|
|
|
// Verify that starting message was logged
|
|
loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Starting database initialization...")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_WhenDatabaseExists_ShouldLogAndMigrate()
|
|
{
|
|
// Arrange
|
|
var dbPath = $"TestDb_{Guid.NewGuid()}.db";
|
|
var services = new ServiceCollection();
|
|
services.AddDbContext<ChatBotDbContext>(options =>
|
|
options.UseSqlite($"Data Source={dbPath}")
|
|
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
|
|
);
|
|
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
|
|
var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object);
|
|
|
|
try
|
|
{
|
|
// Act
|
|
await service.StartAsync(CancellationToken.None);
|
|
|
|
// Assert
|
|
loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Starting database initialization")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
|
|
loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) =>
|
|
v.ToString()!
|
|
.Contains("Database initialization completed successfully")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
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<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
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<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Database initialization service stopped")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StopAsync_WhenCancellationRequested_ShouldStillComplete()
|
|
{
|
|
// Arrange
|
|
var serviceProviderMock = new Mock<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
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<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Database initialization service stopped")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_ShouldHandleDatabaseDoesNotExistException()
|
|
{
|
|
// Arrange
|
|
var dbPath = $"TestDb_{Guid.NewGuid()}.db";
|
|
var services = new ServiceCollection();
|
|
services.AddDbContext<ChatBotDbContext>(options =>
|
|
options.UseSqlite($"Data Source={dbPath}")
|
|
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
|
|
);
|
|
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
|
|
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<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) =>
|
|
v.ToString()!
|
|
.Contains("Database initialization completed successfully")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
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<ChatBotDbContext>(options =>
|
|
options.UseSqlite($"Data Source={dbPath}")
|
|
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
|
|
);
|
|
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
|
|
var service = new DatabaseInitializationService(serviceProvider, loggerMock.Object);
|
|
|
|
try
|
|
{
|
|
// Act
|
|
await service.StartAsync(CancellationToken.None);
|
|
|
|
// Assert
|
|
loggerMock.Verify(
|
|
x =>
|
|
x.Log(
|
|
LogLevel.Information,
|
|
It.IsAny<EventId>(),
|
|
It.Is<It.IsAnyType>((v, t) => true), // Any log message
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
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<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
|
|
// Act
|
|
var service = new DatabaseInitializationService(
|
|
serviceProviderMock.Object,
|
|
loggerMock.Object
|
|
);
|
|
|
|
// Assert
|
|
service.Should().BeAssignableTo<Microsoft.Extensions.Hosting.IHostedService>();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartAsync_MultipleCallsInSequence_ShouldWork()
|
|
{
|
|
// Arrange
|
|
var dbPath = $"TestDb_{Guid.NewGuid()}.db";
|
|
var services = new ServiceCollection();
|
|
services.AddDbContext<ChatBotDbContext>(options =>
|
|
options.UseSqlite($"Data Source={dbPath}")
|
|
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
|
|
);
|
|
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
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<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) =>
|
|
v.ToString()!
|
|
.Contains("Database initialization completed successfully")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
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<IServiceProvider>();
|
|
var loggerMock = new Mock<ILogger<DatabaseInitializationService>>();
|
|
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<EventId>(),
|
|
It.Is<It.IsAnyType>(
|
|
(v, t) => v.ToString()!.Contains("Database initialization service stopped")
|
|
),
|
|
It.IsAny<Exception>(),
|
|
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
|
|
),
|
|
Times.Once
|
|
);
|
|
}
|
|
}
|