using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Omika.Diagnostics.Configuration;
using Omika.Diagnostics.Data;
using Omika.Diagnostics.Import;
using Omika.Diagnostics.Worker;
namespace Omika.Diagnostics.Tests;
///
/// Vérifie que le conteneur DI du Diagnostics Worker construit réellement
/// et les hosted services (régression de
/// l'incident production « Unable to resolve service for type
/// DiagnosticImportOptions »). Le host est construit avec
/// ValidateOnBuild + ValidateScopes pour détecter ce type d'erreur
/// à la construction (le test échouait avec l'ancien code).
///
public class DiagnosticWorkerDependencyInjectionTests
{
[Fact]
public void WorkerHost_ServiceProviderBuilds_WithValidation()
{
// Avec l'ancien constructeur (DiagnosticImportOptions concret), ce Build()
// levait « Unable to resolve service for type DiagnosticImportOptions ».
using IHost host = BuildWorkerHost();
Assert.NotNull(host.Services);
}
[Fact]
public void WorkerHost_DiagnosticImporter_IsResolvable()
{
using IHost host = BuildWorkerHost();
DiagnosticImporter importer = host.Services.GetRequiredService();
Assert.NotNull(importer);
}
[Fact]
public void WorkerHost_DiagnosticImportOptions_AreResolvedAndBound()
{
using IHost host = BuildWorkerHost();
DiagnosticImportOptions options = host.Services
.GetRequiredService>()
.Value;
Assert.NotNull(options);
// La configuration in-memory prime sur les valeurs par défaut (BatchSize=100).
Assert.Equal(50, options.BatchSize);
Assert.Equal("import-checkpoint.json", options.CheckpointFileName);
Assert.Equal(@"C:\Omika\Data\DiagnosticsWorker\Checkpoint", options.CheckpointDirectory);
}
[Fact]
public void WorkerHost_DiagnosticStoreOptions_ConnectionStringOverride_Wins()
{
using IHost host = BuildWorkerHost();
DiagnosticStoreOptions store = host.Services
.GetRequiredService>()
.Value;
// La surcharge standard ConnectionStrings:DiagnosticsDatabase (variable
// d'environnement serveur) prime sur Diagnostics:ConnectionString.
Assert.Equal(
"Host=localhost;Database=omika_diagnostics;Username=diagnostics_worker",
store.ConnectionString);
}
[Fact]
public void WorkerHost_HostedServices_AreResolvable()
{
using IHost host = BuildWorkerHost();
List hosted = host.Services.GetServices().ToList();
Assert.Contains(hosted, s => s is DiagnosticImportWorker);
Assert.Contains(hosted, s => s is DiagnosticRetentionWorker);
Assert.Contains(hosted, s => s is DiagnosticTelemetryRetentionWorker);
}
private static IHost BuildWorkerHost()
{
// Configuration in-memory : aucune valeur secrète. La chaîne PostgreSQL
// est un placeholder sans mot de passe (jamais ouverte : les repositories
// sont construits sans connexion).
IConfiguration configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary
{
["Diagnostics:ConnectionString"] = "Host=ignored;Database=ignored",
["Diagnostics:Import:BatchSize"] = "50",
["Diagnostics:Import:CheckpointFileName"] = "import-checkpoint.json",
["Diagnostics:Import:CheckpointDirectory"] = @"C:\Omika\Data\DiagnosticsWorker\Checkpoint",
["Observability:Remote:Directory"] = @"C:\Omika\Data\Api\Diagnostics\Jsonl",
// Surcharge standard (serveur) : prime sur Diagnostics:ConnectionString.
["ConnectionStrings:DiagnosticsDatabase"] = "Host=localhost;Database=omika_diagnostics;Username=diagnostics_worker"
})
.Build();
// Reproduit Program.cs du Diagnostics Worker, avec validation DI stricte
// (ValidateOnBuild + ValidateScopes) pour piéger les dépendances non
// enregistrées.
return new HostBuilder()
.UseDefaultServiceProvider(options =>
{
options.ValidateOnBuild = true;
options.ValidateScopes = true;
})
.ConfigureServices(services =>
{
services.AddLogging();
services.AddOmikaDiagnosticData(configuration);
services.AddHostedService();
services.AddHostedService();
services.AddHostedService();
})
.Build();
}
}