refactor(auditlog): consolidate AuditEvent DTO mappers into Communication
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ScadaLink.AuditLog.Tests.Integration.Infrastructure;
|
||||
using ScadaLink.AuditLog.Telemetry;
|
||||
using ScadaLink.Commons.Entities.Audit;
|
||||
using ScadaLink.Commons.Messages.Integration;
|
||||
using ScadaLink.Commons.Types;
|
||||
@@ -55,7 +54,7 @@ public class CombinedTelemetryIdempotencyTests : TestKit, IClassFixture<MsSqlMig
|
||||
{
|
||||
var dto = new CachedTelemetryPacket
|
||||
{
|
||||
AuditEvent = AuditEventMapper.ToDto(new AuditEvent
|
||||
AuditEvent = AuditEventDtoMapper.ToDto(new AuditEvent
|
||||
{
|
||||
EventId = eventId,
|
||||
OccurredAtUtc = nowUtc,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using ScadaLink.AuditLog.Site.Telemetry;
|
||||
using ScadaLink.AuditLog.Telemetry;
|
||||
using ScadaLink.Commons.Interfaces.Services;
|
||||
using ScadaLink.Commons.Messages.Integration;
|
||||
using ScadaLink.Commons.Types;
|
||||
@@ -88,7 +87,7 @@ public sealed class CombinedTelemetryDispatcher : ICachedCallTelemetryForwarder
|
||||
{
|
||||
return new CachedTelemetryPacket
|
||||
{
|
||||
AuditEvent = AuditEventMapper.ToDto(telemetry.Audit),
|
||||
AuditEvent = AuditEventDtoMapper.ToDto(telemetry.Audit),
|
||||
Operational = ToOperationalDto(telemetry.Operational),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Akka.Actor;
|
||||
using ScadaLink.AuditLog.Site.Telemetry;
|
||||
using ScadaLink.AuditLog.Telemetry;
|
||||
using ScadaLink.Commons.Entities.Audit;
|
||||
using ScadaLink.Commons.Messages.Audit;
|
||||
using ScadaLink.Commons.Types;
|
||||
@@ -88,7 +87,7 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
|
||||
var events = new List<AuditEvent>(batch.Events.Count);
|
||||
foreach (var dto in batch.Events)
|
||||
{
|
||||
events.Add(AuditEventMapper.FromDto(dto));
|
||||
events.Add(AuditEventDtoMapper.FromDto(dto));
|
||||
}
|
||||
|
||||
// Ask the central actor; the reply carries the accepted EventIds.
|
||||
@@ -114,7 +113,7 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
|
||||
/// back into the proto ack.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Uses the shared <see cref="AuditEventMapper.FromDto"/> for the audit half;
|
||||
/// Uses the shared <see cref="AuditEventDtoMapper.FromDto"/> for the audit half;
|
||||
/// the SiteCall DTO is decoded inline because the AuditLog mapper does not
|
||||
/// (and should not) know about <see cref="SiteCallOperationalDto"/> — the
|
||||
/// production gRPC server (Bundle D) uses the same inline shape.
|
||||
@@ -132,7 +131,7 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
|
||||
var entries = new List<CachedTelemetryEntry>(batch.Packets.Count);
|
||||
foreach (var packet in batch.Packets)
|
||||
{
|
||||
var audit = AuditEventMapper.FromDto(packet.AuditEvent);
|
||||
var audit = AuditEventDtoMapper.FromDto(packet.AuditEvent);
|
||||
var siteCall = MapSiteCallFromDto(packet.Operational);
|
||||
entries.Add(new CachedTelemetryEntry(audit, siteCall));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Akka.Actor;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using ScadaLink.AuditLog.Site.Telemetry;
|
||||
using ScadaLink.AuditLog.Telemetry;
|
||||
using ScadaLink.Commons.Entities.Audit;
|
||||
using ScadaLink.Commons.Messages.Audit;
|
||||
using ScadaLink.Commons.Types.Enums;
|
||||
@@ -46,7 +45,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
|
||||
var batch = new AuditEventBatch();
|
||||
foreach (var e in events)
|
||||
{
|
||||
batch.Events.Add(AuditEventMapper.ToDto(e));
|
||||
batch.Events.Add(AuditEventDtoMapper.ToDto(e));
|
||||
}
|
||||
return batch;
|
||||
}
|
||||
@@ -158,7 +157,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
|
||||
{
|
||||
batch.Packets.Add(new CachedTelemetryPacket
|
||||
{
|
||||
AuditEvent = AuditEventMapper.ToDto(e),
|
||||
AuditEvent = AuditEventDtoMapper.ToDto(e),
|
||||
Operational = NewOperationalDto(),
|
||||
});
|
||||
}
|
||||
@@ -190,7 +189,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
|
||||
var batch = new CachedTelemetryBatch();
|
||||
batch.Packets.Add(new CachedTelemetryPacket
|
||||
{
|
||||
AuditEvent = AuditEventMapper.ToDto(NewEvent()),
|
||||
AuditEvent = AuditEventDtoMapper.ToDto(NewEvent()),
|
||||
Operational = NewOperationalDto(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using ScadaLink.AuditLog.Telemetry;
|
||||
using ScadaLink.Commons.Entities.Audit;
|
||||
using ScadaLink.Commons.Types.Enums;
|
||||
using ScadaLink.Communication.Grpc;
|
||||
|
||||
namespace ScadaLink.AuditLog.Tests.Telemetry;
|
||||
|
||||
/// <summary>
|
||||
/// Round-trip + edge tests for the <see cref="AuditEventMapper"/> that bridges
|
||||
/// <see cref="AuditEvent"/> (Commons) ↔ <see cref="AuditEventDto"/> (proto).
|
||||
/// ForwardState is site-local and IngestedAtUtc is central-set, so neither survives
|
||||
/// the proto round-trip.
|
||||
/// </summary>
|
||||
public class AuditEventMapperTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToDto_FromDto_Roundtrip_FullyPopulated_PreservesAllFields()
|
||||
{
|
||||
var occurredAt = new DateTime(2026, 5, 20, 10, 15, 30, 123, DateTimeKind.Utc);
|
||||
var ingestedAt = new DateTime(2026, 5, 20, 10, 15, 31, 0, DateTimeKind.Utc);
|
||||
var correlationId = Guid.NewGuid();
|
||||
var eventId = Guid.NewGuid();
|
||||
|
||||
var original = new AuditEvent
|
||||
{
|
||||
EventId = eventId,
|
||||
OccurredAtUtc = occurredAt,
|
||||
IngestedAtUtc = ingestedAt,
|
||||
Channel = AuditChannel.ApiOutbound,
|
||||
Kind = AuditKind.ApiCallCached,
|
||||
CorrelationId = correlationId,
|
||||
SourceSiteId = "site-1",
|
||||
SourceInstanceId = "Pump01",
|
||||
SourceScript = "OnDemand",
|
||||
Actor = "design-key",
|
||||
Target = "weather-api",
|
||||
Status = AuditStatus.Forwarded,
|
||||
HttpStatus = 200,
|
||||
DurationMs = 42,
|
||||
ErrorMessage = "transient timeout",
|
||||
ErrorDetail = "stack-trace",
|
||||
RequestSummary = "GET /weather",
|
||||
ResponseSummary = "{ \"ok\": true }",
|
||||
PayloadTruncated = true,
|
||||
Extra = "{ \"retryCount\": 1 }",
|
||||
ForwardState = AuditForwardState.Pending
|
||||
};
|
||||
|
||||
var dto = AuditEventMapper.ToDto(original);
|
||||
var roundTripped = AuditEventMapper.FromDto(dto);
|
||||
|
||||
Assert.Equal(original.EventId, roundTripped.EventId);
|
||||
Assert.Equal(original.OccurredAtUtc, roundTripped.OccurredAtUtc);
|
||||
Assert.Equal(original.Channel, roundTripped.Channel);
|
||||
Assert.Equal(original.Kind, roundTripped.Kind);
|
||||
Assert.Equal(original.CorrelationId, roundTripped.CorrelationId);
|
||||
Assert.Equal(original.SourceSiteId, roundTripped.SourceSiteId);
|
||||
Assert.Equal(original.SourceInstanceId, roundTripped.SourceInstanceId);
|
||||
Assert.Equal(original.SourceScript, roundTripped.SourceScript);
|
||||
Assert.Equal(original.Actor, roundTripped.Actor);
|
||||
Assert.Equal(original.Target, roundTripped.Target);
|
||||
Assert.Equal(original.Status, roundTripped.Status);
|
||||
Assert.Equal(original.HttpStatus, roundTripped.HttpStatus);
|
||||
Assert.Equal(original.DurationMs, roundTripped.DurationMs);
|
||||
Assert.Equal(original.ErrorMessage, roundTripped.ErrorMessage);
|
||||
Assert.Equal(original.ErrorDetail, roundTripped.ErrorDetail);
|
||||
Assert.Equal(original.RequestSummary, roundTripped.RequestSummary);
|
||||
Assert.Equal(original.ResponseSummary, roundTripped.ResponseSummary);
|
||||
Assert.Equal(original.PayloadTruncated, roundTripped.PayloadTruncated);
|
||||
Assert.Equal(original.Extra, roundTripped.Extra);
|
||||
|
||||
// ForwardState + IngestedAtUtc are NOT carried in the proto contract.
|
||||
Assert.Null(roundTripped.ForwardState);
|
||||
Assert.Null(roundTripped.IngestedAtUtc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToDto_NullableStringFields_BecomeEmptyString()
|
||||
{
|
||||
var evt = new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = DateTime.UtcNow,
|
||||
Channel = AuditChannel.Notification,
|
||||
Kind = AuditKind.NotifySend,
|
||||
Status = AuditStatus.Submitted
|
||||
// all string? fields left null; CorrelationId null
|
||||
};
|
||||
|
||||
var dto = AuditEventMapper.ToDto(evt);
|
||||
|
||||
Assert.Equal(string.Empty, dto.CorrelationId);
|
||||
Assert.Equal(string.Empty, dto.SourceSiteId);
|
||||
Assert.Equal(string.Empty, dto.SourceInstanceId);
|
||||
Assert.Equal(string.Empty, dto.SourceScript);
|
||||
Assert.Equal(string.Empty, dto.Actor);
|
||||
Assert.Equal(string.Empty, dto.Target);
|
||||
Assert.Equal(string.Empty, dto.ErrorMessage);
|
||||
Assert.Equal(string.Empty, dto.ErrorDetail);
|
||||
Assert.Equal(string.Empty, dto.RequestSummary);
|
||||
Assert.Equal(string.Empty, dto.ResponseSummary);
|
||||
Assert.Equal(string.Empty, dto.Extra);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromDto_EmptyString_BecomesNullProperty()
|
||||
{
|
||||
var dto = new AuditEventDto
|
||||
{
|
||||
EventId = Guid.NewGuid().ToString(),
|
||||
OccurredAtUtc = Timestamp.FromDateTime(DateTime.UtcNow),
|
||||
Channel = nameof(AuditChannel.ApiOutbound),
|
||||
Kind = nameof(AuditKind.ApiCall),
|
||||
Status = nameof(AuditStatus.Submitted),
|
||||
CorrelationId = string.Empty,
|
||||
SourceSiteId = string.Empty,
|
||||
SourceInstanceId = string.Empty,
|
||||
SourceScript = string.Empty,
|
||||
Actor = string.Empty,
|
||||
Target = string.Empty,
|
||||
ErrorMessage = string.Empty,
|
||||
ErrorDetail = string.Empty,
|
||||
RequestSummary = string.Empty,
|
||||
ResponseSummary = string.Empty,
|
||||
Extra = string.Empty
|
||||
};
|
||||
|
||||
var evt = AuditEventMapper.FromDto(dto);
|
||||
|
||||
Assert.Null(evt.CorrelationId);
|
||||
Assert.Null(evt.SourceSiteId);
|
||||
Assert.Null(evt.SourceInstanceId);
|
||||
Assert.Null(evt.SourceScript);
|
||||
Assert.Null(evt.Actor);
|
||||
Assert.Null(evt.Target);
|
||||
Assert.Null(evt.ErrorMessage);
|
||||
Assert.Null(evt.ErrorDetail);
|
||||
Assert.Null(evt.RequestSummary);
|
||||
Assert.Null(evt.ResponseSummary);
|
||||
Assert.Null(evt.Extra);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToDto_OccurredAtUtc_PreservesUtcKind()
|
||||
{
|
||||
var occurredAt = new DateTime(2026, 5, 20, 8, 0, 0, DateTimeKind.Utc);
|
||||
var evt = new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = occurredAt,
|
||||
Channel = AuditChannel.DbOutbound,
|
||||
Kind = AuditKind.DbWrite,
|
||||
Status = AuditStatus.Delivered
|
||||
};
|
||||
|
||||
var dto = AuditEventMapper.ToDto(evt);
|
||||
var roundTripped = AuditEventMapper.FromDto(dto);
|
||||
|
||||
Assert.Equal(DateTimeKind.Utc, roundTripped.OccurredAtUtc.Kind);
|
||||
Assert.Equal(occurredAt, roundTripped.OccurredAtUtc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToDto_NullableInt_BecomesNullInt32Value()
|
||||
{
|
||||
var evt = new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = DateTime.UtcNow,
|
||||
Channel = AuditChannel.Notification,
|
||||
Kind = AuditKind.NotifySend,
|
||||
Status = AuditStatus.Submitted,
|
||||
HttpStatus = null,
|
||||
DurationMs = null
|
||||
};
|
||||
|
||||
var dto = AuditEventMapper.ToDto(evt);
|
||||
|
||||
Assert.Null(dto.HttpStatus);
|
||||
Assert.Null(dto.DurationMs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromDto_NullInt32Value_BecomesNullProperty()
|
||||
{
|
||||
var dto = new AuditEventDto
|
||||
{
|
||||
EventId = Guid.NewGuid().ToString(),
|
||||
OccurredAtUtc = Timestamp.FromDateTime(DateTime.UtcNow),
|
||||
Channel = nameof(AuditChannel.ApiInbound),
|
||||
Kind = nameof(AuditKind.InboundRequest),
|
||||
Status = nameof(AuditStatus.Delivered)
|
||||
// HttpStatus + DurationMs intentionally left absent
|
||||
};
|
||||
|
||||
Assert.Null(dto.HttpStatus);
|
||||
Assert.Null(dto.DurationMs);
|
||||
|
||||
var evt = AuditEventMapper.FromDto(dto);
|
||||
|
||||
Assert.Null(evt.HttpStatus);
|
||||
Assert.Null(evt.DurationMs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToDto_EnumValues_StoredAsStringNames()
|
||||
{
|
||||
var evt = new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = DateTime.UtcNow,
|
||||
Channel = AuditChannel.ApiOutbound,
|
||||
Kind = AuditKind.ApiCallCached,
|
||||
Status = AuditStatus.Parked
|
||||
};
|
||||
|
||||
var dto = AuditEventMapper.ToDto(evt);
|
||||
|
||||
Assert.Equal("ApiOutbound", dto.Channel);
|
||||
Assert.Equal("ApiCallCached", dto.Kind);
|
||||
Assert.Equal("Parked", dto.Status);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user