Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
@@ -0,0 +1,16 @@
using Xunit;
namespace Omika.Tests.Runtime;
/// <summary>
/// Collection de tests partageant l'état statique global
/// <see cref="Omika.Core.Runtime.ApplicationRuntimeContext"/>.
///
/// xUnit exécute les tests d'une même collection séquentiellement, ce qui évite
/// que des tests manipulant le contexte statique global (Reset/Configure/
/// SetDetectedVersion) interfèrent entre eux lorsqu'ils s'exécutent en parallèle.
/// </summary>
[CollectionDefinition("ApplicationRuntimeContext")]
public class ApplicationRuntimeContextCollection
{
}
@@ -0,0 +1,364 @@
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<DatabaseSchemaVersion>.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<OperationCanceledException>(
() => 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<DatabaseSchemaVersion> _result;
public FakeDetector(DatabaseSchemaVersion version)
: this(Result<DatabaseSchemaVersion>.Success(version))
{
}
public FakeDetector(Result<DatabaseSchemaVersion> result)
{
_result = result;
}
public int DetectCallCount { get; private set; }
public Task<Result<DatabaseSchemaVersion>> DetectAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
DetectCallCount++;
return Task.FromResult(_result);
}
}
}