using Microsoft.Extensions.Logging; using Omika.Core.Observability; using Omika.Core.Observability.External; using Omika.Core.Observability.Outbox; namespace Omika.Tests.Observability.External; /// Liste blanche et filtrage sécurité du mapping externe (§31/§79/§71). public class OmikaEventExternalMapperTests { [Fact] public void MapsWhitelistFields_AndExcludesIdentity() { var e = new TelemetryEvent { EventCode = TelemetryEventCodes.MobileAppStarted, Level = TelemetryLevel.Warning, Priority = TelemetryPriority.P2, Component = "SyncEngine", TraceId = Guid.NewGuid(), SessionId = Guid.NewGuid(), OperationId = Guid.NewGuid(), AppVersion = "1.2.3", Platform = "Android", ErrorCode = "ERR-1", DurationMs = 123, Success = true, DeviceId = "device-abc", ClientId = "client-1", UserId = "user-1", CollaborateurId = "col-1", EntityId = "entity-1" }; ExternalLogRecord? record = OmikaEventExternalMapper.ToLogRecord(e); Assert.NotNull(record); Assert.Equal(LogLevel.Warning, record.Level); Assert.Equal(TelemetryEventCodes.MobileAppStarted.Value, record.EventCode); Assert.Equal(e.TraceId, record.TraceId); Assert.Contains(record.Attributes, kv => kv.Key == "omika.trace_id"); Assert.Contains(record.Attributes, kv => kv.Key == "omika.session_id"); Assert.Contains(record.Attributes, kv => kv.Key == "omika.operation_id"); Assert.Contains(record.Attributes, kv => kv.Key == "component" && (string?)kv.Value == "SyncEngine"); // Identité métier : JAMAIS exportée (liste blanche externe). Assert.DoesNotContain(record.Attributes, kv => kv.Key == "omika.device_id"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "userid"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "clientid"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "collaborateurid"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "entityid"); } [Fact] public void DropsForbiddenMetadataKeysAndValues() { var e = new TelemetryEvent { EventCode = TelemetryEventCodes.MobileAppStarted, Metadata = TelemetryMetadata.Create( ("ok_key", "safe-value"), ("password", "hunter2"), ("Authorization", "Bearer secret"), ("connectionString", "Server=..." ), ("note", "contains token here")) }; ExternalLogRecord? record = OmikaEventExternalMapper.ToLogRecord(e); Assert.NotNull(record); Assert.Contains(record.Attributes, kv => kv.Key == "ok_key"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "password"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "Authorization"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "connectionString"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "note"); } [Fact] public void ReturnsNullForInvalidEvent() { Assert.Null(OmikaEventExternalMapper.ToLogRecord((OmikaEvent?)null)); // EventCode invalide (défaut) → événement non mappable. var e = new TelemetryEvent(); Assert.Null(OmikaEventExternalMapper.ToLogRecord(e)); } [Fact] public void LogLevelMapping_IsDeterministic() { Assert.Equal(LogLevel.Trace, OmikaEventExternalMapper.ToLogLevel(TelemetryLevel.Trace)); Assert.Equal(LogLevel.Debug, OmikaEventExternalMapper.ToLogLevel(TelemetryLevel.Debug)); Assert.Equal(LogLevel.Information, OmikaEventExternalMapper.ToLogLevel(TelemetryLevel.Info)); Assert.Equal(LogLevel.Warning, OmikaEventExternalMapper.ToLogLevel(TelemetryLevel.Warning)); Assert.Equal(LogLevel.Error, OmikaEventExternalMapper.ToLogLevel(TelemetryLevel.Error)); Assert.Equal(LogLevel.Critical, OmikaEventExternalMapper.ToLogLevel(TelemetryLevel.Critical)); } [Fact] public void MapsDto_WithWhitelist() { var dto = new DiagnosticEventDto { EventId = Guid.NewGuid(), EventCode = "OMK-MOB-SYNC-001", TimestampUtc = DateTimeOffset.UtcNow, Level = "Error", Priority = "P1", Component = "MobileSync", TraceId = Guid.NewGuid(), SessionId = Guid.NewGuid(), DeviceId = "device-abc", ClientId = "client-1", ErrorCode = "ERR-X", Metadata = new Dictionary { ["k"] = "v", ["password"] = "secret" } }; ExternalLogRecord? record = OmikaEventExternalMapper.ToLogRecord(dto); Assert.NotNull(record); Assert.Equal(LogLevel.Error, record.Level); Assert.Contains(record.Attributes, kv => kv.Key == "omika.trace_id"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "deviceid"); Assert.DoesNotContain(record.Attributes, kv => kv.Key == "password"); Assert.Contains(record.Attributes, kv => kv.Key == "k"); } }