refactor(auditlog): consolidate SiteCall DTO mapper into Communication

Extract the verbatim-duplicated SiteCallOperationalDto -> SiteCall mapper
into a single public SiteCallDtoMapper static class in
ScadaLink.Communication.Grpc, mirroring AuditEventDtoMapper. Replaces three
identical private copies (SiteStreamGrpcServer.MapSiteCallFromDto,
ClusterClientSiteAuditClient.MapSiteCall, and the test-infra
DirectActorSiteStreamAuditClient.MapSiteCallFromDto), removes the now-stale
doc comment that justified the duplication, and drops the using directives
that became unused. Adds SiteCallDtoMapperTests for field-by-field coverage.

Only the FromDto direction is provided: nothing maps SiteCall back onto the
wire, so a ToDto would be dead code.
This commit is contained in:
Joseph Doherty
2026-05-21 04:00:20 -04:00
parent fdd1a4b886
commit 6f0d2ca499
5 changed files with 211 additions and 86 deletions

View File

@@ -2,7 +2,6 @@ using Akka.Actor;
using ScadaLink.AuditLog.Site.Telemetry;
using ScadaLink.Commons.Entities.Audit;
using ScadaLink.Commons.Messages.Audit;
using ScadaLink.Commons.Types;
using ScadaLink.Communication.Grpc;
namespace ScadaLink.AuditLog.Tests.Integration.Infrastructure;
@@ -113,10 +112,9 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
/// back into the proto ack.
/// </summary>
/// <remarks>
/// 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.
/// Uses the shared <see cref="AuditEventDtoMapper.FromDto"/> for the audit half
/// and <see cref="SiteCallDtoMapper.FromDto"/> for the SiteCall half — the same
/// canonical mappers the production <c>SiteStreamGrpcServer</c> uses.
/// </remarks>
public async Task<IngestAck> IngestCachedTelemetryAsync(CachedTelemetryBatch batch, CancellationToken ct)
{
@@ -132,7 +130,7 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
foreach (var packet in batch.Packets)
{
var audit = AuditEventDtoMapper.FromDto(packet.AuditEvent);
var siteCall = MapSiteCallFromDto(packet.Operational);
var siteCall = SiteCallDtoMapper.FromDto(packet.Operational);
entries.Add(new CachedTelemetryEntry(audit, siteCall));
}
@@ -149,28 +147,4 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
}
return ack;
}
/// <summary>
/// Mirrors <c>SiteStreamGrpcServer.MapSiteCallFromDto</c> — keep the two in
/// sync. The placeholder <see cref="SiteCall.IngestedAtUtc"/> stamped here
/// is overwritten by the central ingest actor inside the dual-write
/// transaction, so the value sent on the wire is informational only.
/// </summary>
private static SiteCall MapSiteCallFromDto(SiteCallOperationalDto dto) => new()
{
TrackedOperationId = TrackedOperationId.Parse(dto.TrackedOperationId),
Channel = dto.Channel,
Target = dto.Target,
SourceSite = dto.SourceSite,
Status = dto.Status,
RetryCount = dto.RetryCount,
LastError = string.IsNullOrEmpty(dto.LastError) ? null : dto.LastError,
HttpStatus = dto.HttpStatus,
CreatedAtUtc = DateTime.SpecifyKind(dto.CreatedAtUtc.ToDateTime(), DateTimeKind.Utc),
UpdatedAtUtc = DateTime.SpecifyKind(dto.UpdatedAtUtc.ToDateTime(), DateTimeKind.Utc),
TerminalAtUtc = dto.TerminalAtUtc is null
? null
: DateTime.SpecifyKind(dto.TerminalAtUtc.ToDateTime(), DateTimeKind.Utc),
IngestedAtUtc = DateTime.UtcNow,
};
}