using Microsoft.Extensions.DependencyInjection; using Omika.Core.Abstractions; using Omika.Core.Compatibility; using Omika.Core.Runtime; using Omika.Data; using Omika.Data.Runtime; using Omika.Data.SchemaDetection; namespace Omika.Tests.Runtime; [Collection("ApplicationRuntimeContext")] public class DatabaseRuntimeInitializerTests { // --- Auto + détecté Batik => succès -------------------------------------- [Fact] public async Task InitializeAsync_AutoWithDetectedBatik_ReturnsSuccess() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var detector = new FakeDetector(DatabaseSchemaVersion.Batik); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsSuccess); Assert.True(ApplicationRuntimeContext.Current.IsBatik); Assert.False(ApplicationRuntimeContext.Current.IsOmika); Assert.Equal(DatabaseSchemaVersion.Batik, ApplicationRuntimeContext.Current.DetectedVersion); } // --- Auto + détecté Omika => succès -------------------------------------- [Fact] public async Task InitializeAsync_AutoWithDetectedOmika_ReturnsSuccess() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var detector = new FakeDetector(DatabaseSchemaVersion.Omika); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsSuccess); Assert.True(ApplicationRuntimeContext.Current.IsOmika); Assert.False(ApplicationRuntimeContext.Current.IsBatik); Assert.Equal(DatabaseSchemaVersion.Omika, ApplicationRuntimeContext.Current.DetectedVersion); } // --- Batik + détecté Batik => succès ------------------------------------- [Fact] public async Task InitializeAsync_BatikWithDetectedBatik_ReturnsSuccess() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Batik); var detector = new FakeDetector(DatabaseSchemaVersion.Batik); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsSuccess); Assert.True(ApplicationRuntimeContext.Current.IsBatik); Assert.Equal(DatabaseSchemaVersion.Batik, ApplicationRuntimeContext.Current.DetectedVersion); } // --- Omika + détecté Omika => succès ------------------------------------- [Fact] public async Task InitializeAsync_OmikaWithDetectedOmika_ReturnsSuccess() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Omika); var detector = new FakeDetector(DatabaseSchemaVersion.Omika); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsSuccess); Assert.True(ApplicationRuntimeContext.Current.IsOmika); Assert.Equal(DatabaseSchemaVersion.Omika, ApplicationRuntimeContext.Current.DetectedVersion); } // --- Batik + détecté Omika => échec -------------------------------------- [Fact] public async Task InitializeAsync_BatikWithDetectedOmika_ReturnsFailureAndKeepsUnknown() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Batik); var detector = new FakeDetector(DatabaseSchemaVersion.Omika); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsFailure); Assert.NotEmpty(result.Errors); Assert.Equal(DatabaseSchemaVersion.Unknown, ApplicationRuntimeContext.Current.DetectedVersion); Assert.False(ApplicationRuntimeContext.Current.IsBatik); Assert.False(ApplicationRuntimeContext.Current.IsOmika); } // --- Omika + détecté Batik => échec -------------------------------------- [Fact] public async Task InitializeAsync_OmikaWithDetectedBatik_ReturnsFailureAndKeepsUnknown() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Omika); var detector = new FakeDetector(DatabaseSchemaVersion.Batik); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsFailure); Assert.NotEmpty(result.Errors); Assert.Equal(DatabaseSchemaVersion.Unknown, ApplicationRuntimeContext.Current.DetectedVersion); Assert.False(ApplicationRuntimeContext.Current.IsBatik); Assert.False(ApplicationRuntimeContext.Current.IsOmika); } // --- Unknown => échec ---------------------------------------------------- [Fact] public async Task InitializeAsync_DetectedUnknown_ReturnsFailureAndKeepsUnknown() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var detector = new FakeDetector(DatabaseSchemaVersion.Unknown); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsFailure); Assert.NotEmpty(result.Errors); Assert.Equal(DatabaseSchemaVersion.Unknown, ApplicationRuntimeContext.Current.DetectedVersion); Assert.False(ApplicationRuntimeContext.Current.IsBatik); Assert.False(ApplicationRuntimeContext.Current.IsOmika); } // --- échec du détecteur => runtime non activé ---------------------------- [Fact] public async Task InitializeAsync_DetectorFailure_ReturnsFailureAndDoesNotActivateRuntime() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var detector = new FakeDetector(Result.Failure("Échec de lecture des métadonnées.")); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsFailure); Assert.Contains("métadonnées", result.Errors[0], StringComparison.OrdinalIgnoreCase); Assert.Equal(DatabaseSchemaVersion.Unknown, ApplicationRuntimeContext.Current.DetectedVersion); Assert.False(ApplicationRuntimeContext.Current.IsBatik); Assert.False(ApplicationRuntimeContext.Current.IsOmika); } // --- cancellation => propagation correcte -------------------------------- [Fact] public async Task InitializeAsync_CancellationRequested_Propagates() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var detector = new FakeDetector(DatabaseSchemaVersion.Batik); var initializer = new DatabaseRuntimeInitializer(detector); using var cts = new CancellationTokenSource(); cts.Cancel(); await Assert.ThrowsAnyAsync( () => initializer.InitializeAsync(cts.Token)); } // --- détecteur appelé une seule fois par initialisation ------------------ [Fact] public async Task InitializeAsync_DetectorCalledExactlyOnce() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var detector = new FakeDetector(DatabaseSchemaVersion.Omika); var initializer = new DatabaseRuntimeInitializer(detector); await initializer.InitializeAsync(); Assert.Equal(1, detector.DetectCallCount); } // --- DetectedVersion renseigné uniquement après succès ------------------- [Fact] public async Task InitializeAsync_DetectedVersionSetOnlyOnSuccess() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Omika); var detector = new FakeDetector(DatabaseSchemaVersion.Batik); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsFailure); Assert.Equal(DatabaseSchemaVersion.Unknown, ApplicationRuntimeContext.Current.DetectedVersion); } // --- IsBatik correct après Batik ----------------------------------------- [Fact] public async Task InitializeAsync_AfterBatik_IsBatikTrue() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var initializer = new DatabaseRuntimeInitializer(new FakeDetector(DatabaseSchemaVersion.Batik)); await initializer.InitializeAsync(); Assert.True(ApplicationRuntimeContext.Current.IsBatik); Assert.False(ApplicationRuntimeContext.Current.IsOmika); } // --- IsOmika correct après Omika ----------------------------------------- [Fact] public async Task InitializeAsync_AfterOmika_IsOmikaTrue() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var initializer = new DatabaseRuntimeInitializer(new FakeDetector(DatabaseSchemaVersion.Omika)); await initializer.InitializeAsync(); Assert.True(ApplicationRuntimeContext.Current.IsOmika); Assert.False(ApplicationRuntimeContext.Current.IsBatik); } // --- capabilities cohérentes avec la version détectée -------------------- [Fact] public async Task InitializeAsync_AfterOmika_CapabilitiesReflectDetectedVersion() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var initializer = new DatabaseRuntimeInitializer(new FakeDetector(DatabaseSchemaVersion.Omika)); await initializer.InitializeAsync(); var capabilities = ApplicationRuntimeContext.Current.Capabilities; Assert.Equal(DatabaseSchemaVersion.Omika, capabilities.Version); Assert.True(capabilities.SupportsEntityGuids); Assert.True(capabilities.SupportsRoles); } [Fact] public async Task InitializeAsync_AfterBatik_CapabilitiesReflectDetectedVersion() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Auto); var initializer = new DatabaseRuntimeInitializer(new FakeDetector(DatabaseSchemaVersion.Batik)); await initializer.InitializeAsync(); var capabilities = ApplicationRuntimeContext.Current.Capabilities; Assert.Equal(DatabaseSchemaVersion.Batik, capabilities.Version); Assert.False(capabilities.SupportsEntityGuids); Assert.False(capabilities.SupportsRoles); } // --- aucune sélection configurée utilisée comme version détectée --------- [Fact] public async Task InitializeAsync_SelectionIsNeverUsedAsDetectedVersion() { ApplicationRuntimeContext.Reset(); // Sélection Omika, mais la base réellement détectée est Batik. ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Omika); var initializer = new DatabaseRuntimeInitializer(new FakeDetector(DatabaseSchemaVersion.Batik)); var result = await initializer.InitializeAsync(); // La sélection ne doit jamais être prise pour la version détectée : échec. Assert.True(result.IsFailure); Assert.Equal(DatabaseSchemaVersion.Unknown, ApplicationRuntimeContext.Current.DetectedVersion); } // --- aucun secret dans les messages d'erreur ----------------------------- [Fact] public async Task InitializeAsync_ErrorMessagesDoNotContainSecrets() { ApplicationRuntimeContext.Reset(); ApplicationRuntimeContext.Configure(DatabaseSchemaSelection.Batik); var detector = new FakeDetector(DatabaseSchemaVersion.Omika); var initializer = new DatabaseRuntimeInitializer(detector); var result = await initializer.InitializeAsync(); Assert.True(result.IsFailure); foreach (var error in result.Errors) { Assert.DoesNotContain("password", error, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("pwd", error, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("connectionstring", error, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("dsn", error, StringComparison.OrdinalIgnoreCase); } } // --- service d'initialisation enregistré en Scoped ----------------------- [Fact] public void AddOmikaData_RegistersInitializerAsScoped() { var services = new ServiceCollection(); services.AddOmikaData(); var descriptor = services.Single(d => d.ServiceType == typeof(IDatabaseRuntimeInitializer)); Assert.Equal(ServiceLifetime.Scoped, descriptor.Lifetime); Assert.Equal(typeof(DatabaseRuntimeInitializer), descriptor.ImplementationType); } // --- Program utilise un scope correct pour les services Scoped ----------- [Fact] public void AddOmikaData_InitializerDependsOnScopedDetector() { var services = new ServiceCollection(); services.AddOmikaData(); var initializerDescriptor = services.Single(d => d.ServiceType == typeof(IDatabaseRuntimeInitializer)); var detectorDescriptor = services.Single(d => d.ServiceType == typeof(IDatabaseSchemaDetector)); // L'initialisateur consomme le détecteur du scope courant : les deux sont Scoped. Assert.Equal(ServiceLifetime.Scoped, initializerDescriptor.Lifetime); Assert.Equal(ServiceLifetime.Scoped, detectorDescriptor.Lifetime); } // --- Fake de détecteur --------------------------------------------------- private sealed class FakeDetector : IDatabaseSchemaDetector { private readonly Result _result; public FakeDetector(DatabaseSchemaVersion version) : this(Result.Success(version)) { } public FakeDetector(Result result) { _result = result; } public int DetectCallCount { get; private set; } public Task> DetectAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); DetectCallCount++; return Task.FromResult(_result); } } }