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");
}
}