using Microsoft.Extensions.Options; using Omika.Core.Observability.Identity; using Omika.Core.Observability.Outbox; using Omika.Diagnostics.Data; using Omika.Diagnostics.Import; using Omika.Diagnostics.Models; using Omika.Diagnostics.Tests.Fakes; namespace Omika.Diagnostics.Tests; /// /// Tâche 13-E — E12 + §38 : compatibilité d'un JSONL produit par Omika.Worker avec le /// pipeline d'import (JsonlDiagnosticSource → DiagnosticImporter → DiagnosticEventRecord) /// et NON-ÉCRASEMENT du contexte du producteur par Omika.Diagnostics.Worker. /// /// Un événement Worker portant ServerInstance = WKR-SRV-01 doit arriver dans /// DiagnosticEventRecord avec EXACTEMENT WKR-SRV-01, jamais remplacé par /// le nom de machine de l'importeur. Le contexte (ClientCode/ServerInstance/Environment) /// représente le composant qui a PRODUIT l'événement, pas celui qui l'importe. /// Aucun PostgreSQL réel : FakeDiagnosticEventRepository (§51). /// public class WorkerSyncImportContextTests : IDisposable { private readonly string _dir = CreateTempDirectory(); public void Dispose() => DeleteDirectory(_dir); // --------------------------- E12 + §38 : import complet --------------------- [Fact] public async Task WorkerJsonl_ImportsToRecord_PreservingProducerContext() { // 1. Production d'un événement Worker (format RemoteDiagnosticIngestionStore, // fichier omika-mobile-diagnostic-*.jsonl) avec contexte du producteur. Guid operationId = Guid.NewGuid(); Guid eventId = Guid.NewGuid(); var context = new DiagnosticDeploymentContext { ClientCode = "CLIENT-A", ServerInstance = "WKR-SRV-01", Environment = "Production" }; await WriteWorkerEventAsync(eventId, operationId, context); // 2. Import par Omika.Diagnostics.Worker (JsonlDiagnosticSource + importer). var repository = new FakeDiagnosticEventRepository(); var importer = new DiagnosticImporter( new JsonlDiagnosticSource(_dir, "import-checkpoint.json"), repository, Options.Create(new DiagnosticImportOptions { BatchSize = 100, Source = "worker" })); DiagnosticImportResult result = await importer.ImportOnceAsync(); // 3. L'événement est lisible et importé (aucun format incompatible). Assert.True(result.Succeeded); Assert.Equal(1, result.ImportedCount); Assert.Equal(0, result.DroppedCount); // 4. Le contexte du PRODUCTEUR est conservé, jamais remplacé par l'importeur. IReadOnlyList records = await repository.GetByOperationIdAsync(operationId); DiagnosticEventRecord record = Assert.Single(records); Assert.Equal("CLIENT-A", record.ClientCode); Assert.Equal("WKR-SRV-01", record.ServerInstance); Assert.NotEqual(Environment.MachineName, record.ServerInstance); Assert.Equal("Production", record.Environment); Assert.Equal("Omika.Worker", record.Component); Assert.Equal("OMK-WKR-SYNC-001", record.EventCode); Assert.Equal("worker", record.Source); Assert.Equal(operationId, record.OperationId); } // --------------------------- Helpers ----------------------------------------- private async Task WriteWorkerEventAsync( Guid eventId, Guid operationId, DiagnosticDeploymentContext context) { var store = new RemoteDiagnosticIngestionStore(new RemoteDiagnosticOptions { Directory = _dir }); var evt = new DiagnosticEventDto { EventId = eventId, EventCode = "OMK-WKR-SYNC-001", TimestampUtc = DateTimeOffset.UtcNow, Level = "Info", Priority = "P3", Component = "Omika.Worker", OperationId = operationId, AppVersion = "1.0.0.0", Platform = "worker", Source = "worker", EntityType = "Client", EntityId = Guid.NewGuid().ToString("D"), Success = true, DurationMs = 12, Metadata = new Dictionary { ["operationType"] = "Create", ["retryCount"] = 0, ["result"] = "Success" } }; var batch = new DiagnosticBatchDto { Events = [evt] }; await store.StoreAsync(batch, DiagnosticActorKey.Empty, Guid.NewGuid().ToString("D"), DateTimeOffset.UtcNow, CancellationToken.None, context); } private static string CreateTempDirectory() { string dir = Path.Combine(Path.GetTempPath(), "omika-worker-sync-import-tests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir); return dir; } private static void DeleteDirectory(string dir) { try { if (Directory.Exists(dir)) { Directory.Delete(dir, recursive: true); } } catch { // Best-effort. } } }