refactor(auditlog): consolidate AuditEvent DTO mappers into Communication
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
using Akka.Actor;
|
using Akka.Actor;
|
||||||
using ScadaLink.AuditLog.Telemetry;
|
|
||||||
using ScadaLink.Commons.Entities.Audit;
|
using ScadaLink.Commons.Entities.Audit;
|
||||||
using ScadaLink.Commons.Messages.Audit;
|
using ScadaLink.Commons.Messages.Audit;
|
||||||
using ScadaLink.Commons.Types;
|
using ScadaLink.Commons.Types;
|
||||||
@@ -35,7 +34,7 @@ namespace ScadaLink.AuditLog.Site.Telemetry;
|
|||||||
/// The batches arrive as proto DTOs (<see cref="AuditEventBatch"/> /
|
/// The batches arrive as proto DTOs (<see cref="AuditEventBatch"/> /
|
||||||
/// <see cref="CachedTelemetryBatch"/>) because the
|
/// <see cref="CachedTelemetryBatch"/>) because the
|
||||||
/// <see cref="SiteAuditTelemetryActor"/> builds them with
|
/// <see cref="SiteAuditTelemetryActor"/> builds them with
|
||||||
/// <see cref="AuditEventMapper.ToDto"/>. This client converts them back into
|
/// <see cref="AuditEventDtoMapper.ToDto"/>. This client converts them back into
|
||||||
/// the <see cref="AuditEvent"/> / <see cref="SiteCall"/> entities the Akka
|
/// the <see cref="AuditEvent"/> / <see cref="SiteCall"/> entities the Akka
|
||||||
/// command messages carry — the same DTO→entity translation the
|
/// command messages carry — the same DTO→entity translation the
|
||||||
/// <c>SiteStreamGrpcServer</c> performs for the gRPC reconciliation path.
|
/// <c>SiteStreamGrpcServer</c> performs for the gRPC reconciliation path.
|
||||||
@@ -71,7 +70,7 @@ public sealed class ClusterClientSiteAuditClient : ISiteStreamAuditClient
|
|||||||
var events = new List<AuditEvent>(batch.Events.Count);
|
var events = new List<AuditEvent>(batch.Events.Count);
|
||||||
foreach (var dto in batch.Events)
|
foreach (var dto in batch.Events)
|
||||||
{
|
{
|
||||||
events.Add(AuditEventMapper.FromDto(dto));
|
events.Add(AuditEventDtoMapper.FromDto(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask<T> throws AskTimeoutException on timeout and rethrows a
|
// Ask<T> throws AskTimeoutException on timeout and rethrows a
|
||||||
@@ -92,7 +91,7 @@ public sealed class ClusterClientSiteAuditClient : ISiteStreamAuditClient
|
|||||||
var entries = new List<CachedTelemetryEntry>(batch.Packets.Count);
|
var entries = new List<CachedTelemetryEntry>(batch.Packets.Count);
|
||||||
foreach (var packet in batch.Packets)
|
foreach (var packet in batch.Packets)
|
||||||
{
|
{
|
||||||
var audit = AuditEventMapper.FromDto(packet.AuditEvent);
|
var audit = AuditEventDtoMapper.FromDto(packet.AuditEvent);
|
||||||
var siteCall = MapSiteCall(packet.Operational);
|
var siteCall = MapSiteCall(packet.Operational);
|
||||||
entries.Add(new CachedTelemetryEntry(audit, siteCall));
|
entries.Add(new CachedTelemetryEntry(audit, siteCall));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Akka.Actor;
|
using Akka.Actor;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using ScadaLink.AuditLog.Telemetry;
|
|
||||||
using ScadaLink.Commons.Entities.Audit;
|
using ScadaLink.Commons.Entities.Audit;
|
||||||
using ScadaLink.Commons.Interfaces.Services;
|
using ScadaLink.Commons.Interfaces.Services;
|
||||||
using ScadaLink.Communication.Grpc;
|
using ScadaLink.Communication.Grpc;
|
||||||
@@ -136,7 +135,7 @@ public class SiteAuditTelemetryActor : ReceiveActor
|
|||||||
var batch = new AuditEventBatch();
|
var batch = new AuditEventBatch();
|
||||||
foreach (var e in events)
|
foreach (var e in events)
|
||||||
{
|
{
|
||||||
batch.Events.Add(AuditEventMapper.ToDto(e));
|
batch.Events.Add(AuditEventDtoMapper.ToDto(e));
|
||||||
}
|
}
|
||||||
return batch;
|
return batch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
using ScadaLink.Commons.Entities.Audit;
|
using ScadaLink.Commons.Entities.Audit;
|
||||||
using ScadaLink.Commons.Types.Enums;
|
using ScadaLink.Commons.Types.Enums;
|
||||||
using ScadaLink.Communication.Grpc;
|
|
||||||
using Timestamp = Google.Protobuf.WellKnownTypes.Timestamp;
|
using Timestamp = Google.Protobuf.WellKnownTypes.Timestamp;
|
||||||
|
|
||||||
namespace ScadaLink.AuditLog.Telemetry;
|
namespace ScadaLink.Communication.Grpc;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bridges Audit Log (#23) rows between the in-process <see cref="AuditEvent"/> record
|
/// Canonical bridge for Audit Log (#23) rows between the in-process
|
||||||
/// and the wire-format <see cref="AuditEventDto"/> exchanged over the
|
/// <see cref="AuditEvent"/> record and the wire-format <see cref="AuditEventDto"/>
|
||||||
/// <c>IngestAuditEvents</c> RPC.
|
/// exchanged over the <c>IngestAuditEvents</c>, <c>IngestCachedTelemetry</c> and
|
||||||
|
/// <c>PullAuditEvents</c> RPCs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// This mapper lives in <c>ScadaLink.Communication</c> (which owns the generated
|
||||||
|
/// <see cref="AuditEventDto"/> and references <c>Commons</c> for
|
||||||
|
/// <see cref="AuditEvent"/>) so both <c>SiteStreamGrpcServer</c> and
|
||||||
|
/// <c>ScadaLink.AuditLog</c> can share one implementation without the
|
||||||
|
/// project-reference cycle that would result from hosting it in
|
||||||
|
/// <c>ScadaLink.AuditLog</c> (AuditLog → Communication, never the reverse).
|
||||||
|
/// </para>
|
||||||
/// <para><b>Lossy by design:</b> the proto contract intentionally omits two fields.</para>
|
/// <para><b>Lossy by design:</b> the proto contract intentionally omits two fields.</para>
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><see cref="AuditEvent.ForwardState"/> — site-local SQLite state, never travels.</item>
|
/// <item><see cref="AuditEvent.ForwardState"/> — site-local SQLite state, never travels.</item>
|
||||||
@@ -22,7 +30,7 @@ namespace ScadaLink.AuditLog.Telemetry;
|
|||||||
/// <c>Int32Value</c> wrapper so they preserve true null semantics.
|
/// <c>Int32Value</c> wrapper so they preserve true null semantics.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static class AuditEventMapper
|
public static class AuditEventDtoMapper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Projects an <see cref="AuditEvent"/> into its wire-format DTO. Null reference
|
/// Projects an <see cref="AuditEvent"/> into its wire-format DTO. Null reference
|
||||||
@@ -8,7 +8,6 @@ using ScadaLink.Commons.Entities.Audit;
|
|||||||
using ScadaLink.Commons.Interfaces.Services;
|
using ScadaLink.Commons.Interfaces.Services;
|
||||||
using ScadaLink.Commons.Messages.Audit;
|
using ScadaLink.Commons.Messages.Audit;
|
||||||
using ScadaLink.Commons.Types;
|
using ScadaLink.Commons.Types;
|
||||||
using ScadaLink.Commons.Types.Enums;
|
|
||||||
using GrpcStatus = Grpc.Core.Status;
|
using GrpcStatus = Grpc.Core.Status;
|
||||||
|
|
||||||
namespace ScadaLink.Communication.Grpc;
|
namespace ScadaLink.Communication.Grpc;
|
||||||
@@ -224,13 +223,10 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// The DTO→entity conversion is inlined here (rather than calling the
|
/// The DTO→entity conversion uses the shared <see cref="AuditEventDtoMapper"/>
|
||||||
/// AuditLog mapper) to avoid a project-reference cycle:
|
/// (hosted in <c>ScadaLink.Communication</c> so both this server and
|
||||||
/// <c>ScadaLink.AuditLog</c> already references
|
/// <c>ScadaLink.AuditLog</c> share one implementation without a
|
||||||
/// <c>ScadaLink.Communication</c>, so the gRPC server cannot reach back
|
/// project-reference cycle).
|
||||||
/// into AuditLog for its mapper. The shape mirrors
|
|
||||||
/// <c>AuditEventMapper.FromDto</c> in <c>ScadaLink.AuditLog.Telemetry</c>;
|
|
||||||
/// the two must evolve together.
|
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// When <see cref="_auditIngestActor"/> is not yet wired (host startup
|
/// When <see cref="_auditIngestActor"/> is not yet wired (host startup
|
||||||
@@ -262,36 +258,10 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
|
|||||||
return new IngestAck();
|
return new IngestAck();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inlined FromDto. Keep in sync with AuditEventMapper.FromDto in
|
|
||||||
// ScadaLink.AuditLog.Telemetry — there is no shared mapper because
|
|
||||||
// doing so would create a project-reference cycle (AuditLog → Communication).
|
|
||||||
var entities = new List<AuditEvent>(request.Events.Count);
|
var entities = new List<AuditEvent>(request.Events.Count);
|
||||||
foreach (var dto in request.Events)
|
foreach (var dto in request.Events)
|
||||||
{
|
{
|
||||||
entities.Add(new AuditEvent
|
entities.Add(AuditEventDtoMapper.FromDto(dto));
|
||||||
{
|
|
||||||
EventId = Guid.Parse(dto.EventId),
|
|
||||||
OccurredAtUtc = DateTime.SpecifyKind(dto.OccurredAtUtc.ToDateTime(), DateTimeKind.Utc),
|
|
||||||
IngestedAtUtc = null,
|
|
||||||
Channel = Enum.Parse<AuditChannel>(dto.Channel),
|
|
||||||
Kind = Enum.Parse<AuditKind>(dto.Kind),
|
|
||||||
CorrelationId = string.IsNullOrEmpty(dto.CorrelationId) ? null : Guid.Parse(dto.CorrelationId),
|
|
||||||
SourceSiteId = NullIfEmpty(dto.SourceSiteId),
|
|
||||||
SourceInstanceId = NullIfEmpty(dto.SourceInstanceId),
|
|
||||||
SourceScript = NullIfEmpty(dto.SourceScript),
|
|
||||||
Actor = NullIfEmpty(dto.Actor),
|
|
||||||
Target = NullIfEmpty(dto.Target),
|
|
||||||
Status = Enum.Parse<AuditStatus>(dto.Status),
|
|
||||||
HttpStatus = dto.HttpStatus,
|
|
||||||
DurationMs = dto.DurationMs,
|
|
||||||
ErrorMessage = NullIfEmpty(dto.ErrorMessage),
|
|
||||||
ErrorDetail = NullIfEmpty(dto.ErrorDetail),
|
|
||||||
RequestSummary = NullIfEmpty(dto.RequestSummary),
|
|
||||||
ResponseSummary = NullIfEmpty(dto.ResponseSummary),
|
|
||||||
PayloadTruncated = dto.PayloadTruncated,
|
|
||||||
Extra = NullIfEmpty(dto.Extra),
|
|
||||||
ForwardState = null,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmd = new IngestAuditEventsCommand(entities);
|
var cmd = new IngestAuditEventsCommand(entities);
|
||||||
@@ -355,7 +325,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
|
|||||||
var entries = new List<CachedTelemetryEntry>(request.Packets.Count);
|
var entries = new List<CachedTelemetryEntry>(request.Packets.Count);
|
||||||
foreach (var packet in request.Packets)
|
foreach (var packet in request.Packets)
|
||||||
{
|
{
|
||||||
var auditEvent = MapAuditEventFromDto(packet.AuditEvent);
|
var auditEvent = AuditEventDtoMapper.FromDto(packet.AuditEvent);
|
||||||
var siteCall = MapSiteCallFromDto(packet.Operational);
|
var siteCall = MapSiteCallFromDto(packet.Operational);
|
||||||
entries.Add(new CachedTelemetryEntry(auditEvent, siteCall));
|
entries.Add(new CachedTelemetryEntry(auditEvent, siteCall));
|
||||||
}
|
}
|
||||||
@@ -450,7 +420,7 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
|
|||||||
};
|
};
|
||||||
foreach (var evt in events)
|
foreach (var evt in events)
|
||||||
{
|
{
|
||||||
response.Events.Add(AuditEventToDto(evt));
|
response.Events.Add(AuditEventDtoMapper.ToDto(evt));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flip to Reconciled AFTER projecting the response so a fault below the
|
// Flip to Reconciled AFTER projecting the response so a fault below the
|
||||||
@@ -481,85 +451,6 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Inlined audit-event entity→DTO translation. Keep in sync with
|
|
||||||
/// <c>AuditEventMapper.ToDto</c> in <c>ScadaLink.AuditLog.Telemetry</c> —
|
|
||||||
/// the project-reference cycle (AuditLog → Communication) prevents calling
|
|
||||||
/// the AuditLog mapper directly. The shape mirrors the FromDto pair above.
|
|
||||||
/// </summary>
|
|
||||||
private static AuditEventDto AuditEventToDto(AuditEvent evt)
|
|
||||||
{
|
|
||||||
var dto = new AuditEventDto
|
|
||||||
{
|
|
||||||
EventId = evt.EventId.ToString(),
|
|
||||||
OccurredAtUtc = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(EnsureUtc(evt.OccurredAtUtc)),
|
|
||||||
Channel = evt.Channel.ToString(),
|
|
||||||
Kind = evt.Kind.ToString(),
|
|
||||||
CorrelationId = evt.CorrelationId?.ToString() ?? string.Empty,
|
|
||||||
SourceSiteId = evt.SourceSiteId ?? string.Empty,
|
|
||||||
SourceInstanceId = evt.SourceInstanceId ?? string.Empty,
|
|
||||||
SourceScript = evt.SourceScript ?? string.Empty,
|
|
||||||
Actor = evt.Actor ?? string.Empty,
|
|
||||||
Target = evt.Target ?? string.Empty,
|
|
||||||
Status = evt.Status.ToString(),
|
|
||||||
ErrorMessage = evt.ErrorMessage ?? string.Empty,
|
|
||||||
ErrorDetail = evt.ErrorDetail ?? string.Empty,
|
|
||||||
RequestSummary = evt.RequestSummary ?? string.Empty,
|
|
||||||
ResponseSummary = evt.ResponseSummary ?? string.Empty,
|
|
||||||
PayloadTruncated = evt.PayloadTruncated,
|
|
||||||
Extra = evt.Extra ?? string.Empty,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (evt.HttpStatus.HasValue) dto.HttpStatus = evt.HttpStatus.Value;
|
|
||||||
if (evt.DurationMs.HasValue) dto.DurationMs = evt.DurationMs.Value;
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DateTime EnsureUtc(DateTime value) =>
|
|
||||||
value.Kind == DateTimeKind.Utc
|
|
||||||
? value
|
|
||||||
: DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc);
|
|
||||||
|
|
||||||
private static string? NullIfEmpty(string? value) =>
|
|
||||||
string.IsNullOrEmpty(value) ? null : value;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Inlined audit-event DTO→entity translation, kept in sync with the
|
|
||||||
/// <see cref="IngestAuditEvents"/> handler above. Extracted to a private
|
|
||||||
/// helper so the M3 dual-write RPC can reuse it without duplicating yet
|
|
||||||
/// another copy. The shape still mirrors
|
|
||||||
/// <c>AuditEventMapper.FromDto</c> in <c>ScadaLink.AuditLog.Telemetry</c>;
|
|
||||||
/// the two must evolve together (the project-reference cycle that
|
|
||||||
/// prevents calling the AuditLog mapper directly is documented on
|
|
||||||
/// <see cref="IngestAuditEvents"/>).
|
|
||||||
/// </summary>
|
|
||||||
private static AuditEvent MapAuditEventFromDto(AuditEventDto dto) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
EventId = Guid.Parse(dto.EventId),
|
|
||||||
OccurredAtUtc = DateTime.SpecifyKind(dto.OccurredAtUtc.ToDateTime(), DateTimeKind.Utc),
|
|
||||||
IngestedAtUtc = null,
|
|
||||||
Channel = Enum.Parse<AuditChannel>(dto.Channel),
|
|
||||||
Kind = Enum.Parse<AuditKind>(dto.Kind),
|
|
||||||
CorrelationId = NullIfEmpty(dto.CorrelationId) is { } cid ? Guid.Parse(cid) : null,
|
|
||||||
SourceSiteId = NullIfEmpty(dto.SourceSiteId),
|
|
||||||
SourceInstanceId = NullIfEmpty(dto.SourceInstanceId),
|
|
||||||
SourceScript = NullIfEmpty(dto.SourceScript),
|
|
||||||
Actor = NullIfEmpty(dto.Actor),
|
|
||||||
Target = NullIfEmpty(dto.Target),
|
|
||||||
Status = Enum.Parse<AuditStatus>(dto.Status),
|
|
||||||
HttpStatus = dto.HttpStatus,
|
|
||||||
DurationMs = dto.DurationMs,
|
|
||||||
ErrorMessage = NullIfEmpty(dto.ErrorMessage),
|
|
||||||
ErrorDetail = NullIfEmpty(dto.ErrorDetail),
|
|
||||||
RequestSummary = NullIfEmpty(dto.RequestSummary),
|
|
||||||
ResponseSummary = NullIfEmpty(dto.ResponseSummary),
|
|
||||||
PayloadTruncated = dto.PayloadTruncated,
|
|
||||||
Extra = NullIfEmpty(dto.Extra),
|
|
||||||
ForwardState = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Translates a <see cref="SiteCallOperationalDto"/> into the persistence
|
/// Translates a <see cref="SiteCallOperationalDto"/> into the persistence
|
||||||
/// entity. <see cref="SiteCall.IngestedAtUtc"/> is stamped here as a
|
/// entity. <see cref="SiteCall.IngestedAtUtc"/> is stamped here as a
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Akka.TestKit.Xunit2;
|
using Akka.TestKit.Xunit2;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using ScadaLink.AuditLog.Tests.Integration.Infrastructure;
|
using ScadaLink.AuditLog.Tests.Integration.Infrastructure;
|
||||||
using ScadaLink.AuditLog.Telemetry;
|
|
||||||
using ScadaLink.Commons.Entities.Audit;
|
using ScadaLink.Commons.Entities.Audit;
|
||||||
using ScadaLink.Commons.Messages.Integration;
|
using ScadaLink.Commons.Messages.Integration;
|
||||||
using ScadaLink.Commons.Types;
|
using ScadaLink.Commons.Types;
|
||||||
@@ -55,7 +54,7 @@ public class CombinedTelemetryIdempotencyTests : TestKit, IClassFixture<MsSqlMig
|
|||||||
{
|
{
|
||||||
var dto = new CachedTelemetryPacket
|
var dto = new CachedTelemetryPacket
|
||||||
{
|
{
|
||||||
AuditEvent = AuditEventMapper.ToDto(new AuditEvent
|
AuditEvent = AuditEventDtoMapper.ToDto(new AuditEvent
|
||||||
{
|
{
|
||||||
EventId = eventId,
|
EventId = eventId,
|
||||||
OccurredAtUtc = nowUtc,
|
OccurredAtUtc = nowUtc,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using ScadaLink.AuditLog.Site.Telemetry;
|
using ScadaLink.AuditLog.Site.Telemetry;
|
||||||
using ScadaLink.AuditLog.Telemetry;
|
|
||||||
using ScadaLink.Commons.Interfaces.Services;
|
using ScadaLink.Commons.Interfaces.Services;
|
||||||
using ScadaLink.Commons.Messages.Integration;
|
using ScadaLink.Commons.Messages.Integration;
|
||||||
using ScadaLink.Commons.Types;
|
using ScadaLink.Commons.Types;
|
||||||
@@ -88,7 +87,7 @@ public sealed class CombinedTelemetryDispatcher : ICachedCallTelemetryForwarder
|
|||||||
{
|
{
|
||||||
return new CachedTelemetryPacket
|
return new CachedTelemetryPacket
|
||||||
{
|
{
|
||||||
AuditEvent = AuditEventMapper.ToDto(telemetry.Audit),
|
AuditEvent = AuditEventDtoMapper.ToDto(telemetry.Audit),
|
||||||
Operational = ToOperationalDto(telemetry.Operational),
|
Operational = ToOperationalDto(telemetry.Operational),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Akka.Actor;
|
using Akka.Actor;
|
||||||
using ScadaLink.AuditLog.Site.Telemetry;
|
using ScadaLink.AuditLog.Site.Telemetry;
|
||||||
using ScadaLink.AuditLog.Telemetry;
|
|
||||||
using ScadaLink.Commons.Entities.Audit;
|
using ScadaLink.Commons.Entities.Audit;
|
||||||
using ScadaLink.Commons.Messages.Audit;
|
using ScadaLink.Commons.Messages.Audit;
|
||||||
using ScadaLink.Commons.Types;
|
using ScadaLink.Commons.Types;
|
||||||
@@ -88,7 +87,7 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
|
|||||||
var events = new List<AuditEvent>(batch.Events.Count);
|
var events = new List<AuditEvent>(batch.Events.Count);
|
||||||
foreach (var dto in batch.Events)
|
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.
|
// Ask the central actor; the reply carries the accepted EventIds.
|
||||||
@@ -114,7 +113,7 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
|
|||||||
/// back into the proto ack.
|
/// back into the proto ack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <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
|
/// the SiteCall DTO is decoded inline because the AuditLog mapper does not
|
||||||
/// (and should not) know about <see cref="SiteCallOperationalDto"/> — the
|
/// (and should not) know about <see cref="SiteCallOperationalDto"/> — the
|
||||||
/// production gRPC server (Bundle D) uses the same inline shape.
|
/// 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);
|
var entries = new List<CachedTelemetryEntry>(batch.Packets.Count);
|
||||||
foreach (var packet in batch.Packets)
|
foreach (var packet in batch.Packets)
|
||||||
{
|
{
|
||||||
var audit = AuditEventMapper.FromDto(packet.AuditEvent);
|
var audit = AuditEventDtoMapper.FromDto(packet.AuditEvent);
|
||||||
var siteCall = MapSiteCallFromDto(packet.Operational);
|
var siteCall = MapSiteCallFromDto(packet.Operational);
|
||||||
entries.Add(new CachedTelemetryEntry(audit, siteCall));
|
entries.Add(new CachedTelemetryEntry(audit, siteCall));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Akka.Actor;
|
|||||||
using Akka.TestKit.Xunit2;
|
using Akka.TestKit.Xunit2;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using ScadaLink.AuditLog.Site.Telemetry;
|
using ScadaLink.AuditLog.Site.Telemetry;
|
||||||
using ScadaLink.AuditLog.Telemetry;
|
|
||||||
using ScadaLink.Commons.Entities.Audit;
|
using ScadaLink.Commons.Entities.Audit;
|
||||||
using ScadaLink.Commons.Messages.Audit;
|
using ScadaLink.Commons.Messages.Audit;
|
||||||
using ScadaLink.Commons.Types.Enums;
|
using ScadaLink.Commons.Types.Enums;
|
||||||
@@ -46,7 +45,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
|
|||||||
var batch = new AuditEventBatch();
|
var batch = new AuditEventBatch();
|
||||||
foreach (var e in events)
|
foreach (var e in events)
|
||||||
{
|
{
|
||||||
batch.Events.Add(AuditEventMapper.ToDto(e));
|
batch.Events.Add(AuditEventDtoMapper.ToDto(e));
|
||||||
}
|
}
|
||||||
return batch;
|
return batch;
|
||||||
}
|
}
|
||||||
@@ -158,7 +157,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
|
|||||||
{
|
{
|
||||||
batch.Packets.Add(new CachedTelemetryPacket
|
batch.Packets.Add(new CachedTelemetryPacket
|
||||||
{
|
{
|
||||||
AuditEvent = AuditEventMapper.ToDto(e),
|
AuditEvent = AuditEventDtoMapper.ToDto(e),
|
||||||
Operational = NewOperationalDto(),
|
Operational = NewOperationalDto(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -190,7 +189,7 @@ public class ClusterClientSiteAuditClientTests : TestKit
|
|||||||
var batch = new CachedTelemetryBatch();
|
var batch = new CachedTelemetryBatch();
|
||||||
batch.Packets.Add(new CachedTelemetryPacket
|
batch.Packets.Add(new CachedTelemetryPacket
|
||||||
{
|
{
|
||||||
AuditEvent = AuditEventMapper.ToDto(NewEvent()),
|
AuditEvent = AuditEventDtoMapper.ToDto(NewEvent()),
|
||||||
Operational = NewOperationalDto(),
|
Operational = NewOperationalDto(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using ScadaLink.AuditLog.Telemetry;
|
|
||||||
using ScadaLink.Commons.Entities.Audit;
|
using ScadaLink.Commons.Entities.Audit;
|
||||||
using ScadaLink.Commons.Types.Enums;
|
using ScadaLink.Commons.Types.Enums;
|
||||||
using ScadaLink.Communication.Grpc;
|
using ScadaLink.Communication.Grpc;
|
||||||
|
|
||||||
namespace ScadaLink.AuditLog.Tests.Telemetry;
|
namespace ScadaLink.Communication.Tests;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Round-trip + edge tests for the <see cref="AuditEventMapper"/> that bridges
|
/// Round-trip + edge tests for the <see cref="AuditEventDtoMapper"/> that bridges
|
||||||
/// <see cref="AuditEvent"/> (Commons) ↔ <see cref="AuditEventDto"/> (proto).
|
/// <see cref="AuditEvent"/> (Commons) ↔ <see cref="AuditEventDto"/> (proto).
|
||||||
/// ForwardState is site-local and IngestedAtUtc is central-set, so neither survives
|
/// ForwardState is site-local and IngestedAtUtc is central-set, so neither survives
|
||||||
/// the proto round-trip.
|
/// the proto round-trip.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AuditEventMapperTests
|
public class AuditEventDtoMapperTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ToDto_FromDto_Roundtrip_FullyPopulated_PreservesAllFields()
|
public void ToDto_FromDto_Roundtrip_FullyPopulated_PreservesAllFields()
|
||||||
@@ -47,8 +46,8 @@ public class AuditEventMapperTests
|
|||||||
ForwardState = AuditForwardState.Pending
|
ForwardState = AuditForwardState.Pending
|
||||||
};
|
};
|
||||||
|
|
||||||
var dto = AuditEventMapper.ToDto(original);
|
var dto = AuditEventDtoMapper.ToDto(original);
|
||||||
var roundTripped = AuditEventMapper.FromDto(dto);
|
var roundTripped = AuditEventDtoMapper.FromDto(dto);
|
||||||
|
|
||||||
Assert.Equal(original.EventId, roundTripped.EventId);
|
Assert.Equal(original.EventId, roundTripped.EventId);
|
||||||
Assert.Equal(original.OccurredAtUtc, roundTripped.OccurredAtUtc);
|
Assert.Equal(original.OccurredAtUtc, roundTripped.OccurredAtUtc);
|
||||||
@@ -88,7 +87,7 @@ public class AuditEventMapperTests
|
|||||||
// all string? fields left null; CorrelationId null
|
// all string? fields left null; CorrelationId null
|
||||||
};
|
};
|
||||||
|
|
||||||
var dto = AuditEventMapper.ToDto(evt);
|
var dto = AuditEventDtoMapper.ToDto(evt);
|
||||||
|
|
||||||
Assert.Equal(string.Empty, dto.CorrelationId);
|
Assert.Equal(string.Empty, dto.CorrelationId);
|
||||||
Assert.Equal(string.Empty, dto.SourceSiteId);
|
Assert.Equal(string.Empty, dto.SourceSiteId);
|
||||||
@@ -126,7 +125,7 @@ public class AuditEventMapperTests
|
|||||||
Extra = string.Empty
|
Extra = string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
var evt = AuditEventMapper.FromDto(dto);
|
var evt = AuditEventDtoMapper.FromDto(dto);
|
||||||
|
|
||||||
Assert.Null(evt.CorrelationId);
|
Assert.Null(evt.CorrelationId);
|
||||||
Assert.Null(evt.SourceSiteId);
|
Assert.Null(evt.SourceSiteId);
|
||||||
@@ -154,8 +153,8 @@ public class AuditEventMapperTests
|
|||||||
Status = AuditStatus.Delivered
|
Status = AuditStatus.Delivered
|
||||||
};
|
};
|
||||||
|
|
||||||
var dto = AuditEventMapper.ToDto(evt);
|
var dto = AuditEventDtoMapper.ToDto(evt);
|
||||||
var roundTripped = AuditEventMapper.FromDto(dto);
|
var roundTripped = AuditEventDtoMapper.FromDto(dto);
|
||||||
|
|
||||||
Assert.Equal(DateTimeKind.Utc, roundTripped.OccurredAtUtc.Kind);
|
Assert.Equal(DateTimeKind.Utc, roundTripped.OccurredAtUtc.Kind);
|
||||||
Assert.Equal(occurredAt, roundTripped.OccurredAtUtc);
|
Assert.Equal(occurredAt, roundTripped.OccurredAtUtc);
|
||||||
@@ -175,7 +174,7 @@ public class AuditEventMapperTests
|
|||||||
DurationMs = null
|
DurationMs = null
|
||||||
};
|
};
|
||||||
|
|
||||||
var dto = AuditEventMapper.ToDto(evt);
|
var dto = AuditEventDtoMapper.ToDto(evt);
|
||||||
|
|
||||||
Assert.Null(dto.HttpStatus);
|
Assert.Null(dto.HttpStatus);
|
||||||
Assert.Null(dto.DurationMs);
|
Assert.Null(dto.DurationMs);
|
||||||
@@ -197,7 +196,7 @@ public class AuditEventMapperTests
|
|||||||
Assert.Null(dto.HttpStatus);
|
Assert.Null(dto.HttpStatus);
|
||||||
Assert.Null(dto.DurationMs);
|
Assert.Null(dto.DurationMs);
|
||||||
|
|
||||||
var evt = AuditEventMapper.FromDto(dto);
|
var evt = AuditEventDtoMapper.FromDto(dto);
|
||||||
|
|
||||||
Assert.Null(evt.HttpStatus);
|
Assert.Null(evt.HttpStatus);
|
||||||
Assert.Null(evt.DurationMs);
|
Assert.Null(evt.DurationMs);
|
||||||
@@ -215,7 +214,7 @@ public class AuditEventMapperTests
|
|||||||
Status = AuditStatus.Parked
|
Status = AuditStatus.Parked
|
||||||
};
|
};
|
||||||
|
|
||||||
var dto = AuditEventMapper.ToDto(evt);
|
var dto = AuditEventDtoMapper.ToDto(evt);
|
||||||
|
|
||||||
Assert.Equal("ApiOutbound", dto.Channel);
|
Assert.Equal("ApiOutbound", dto.Channel);
|
||||||
Assert.Equal("ApiCallCached", dto.Kind);
|
Assert.Equal("ApiCallCached", dto.Kind);
|
||||||
Reference in New Issue
Block a user