feat(comm): add source_node field to AuditEventDto + SiteCallOperationalDto proto
- AuditEventDto field 22, SiteCallOperationalDto field 12. Both follow the existing empty-string-means-null convention. - Mappers carry SourceNode end-to-end; round-trip tests cover both populated and null cases.
This commit is contained in:
@@ -100,6 +100,7 @@ public sealed class CombinedTelemetryDispatcher : ICachedCallTelemetryForwarder
|
||||
Channel = op.Channel,
|
||||
Target = op.Target,
|
||||
SourceSite = op.SourceSite,
|
||||
SourceNode = op.SourceNode ?? string.Empty,
|
||||
Status = op.Status,
|
||||
RetryCount = op.RetryCount,
|
||||
LastError = op.LastError ?? string.Empty,
|
||||
|
||||
@@ -34,6 +34,7 @@ public class AuditEventDtoMapperTests
|
||||
ExecutionId = executionId,
|
||||
ParentExecutionId = parentExecutionId,
|
||||
SourceSiteId = "site-1",
|
||||
SourceNode = "node-a",
|
||||
SourceInstanceId = "Pump01",
|
||||
SourceScript = "OnDemand",
|
||||
Actor = "design-key",
|
||||
@@ -61,6 +62,7 @@ public class AuditEventDtoMapperTests
|
||||
Assert.Equal(original.ExecutionId, roundTripped.ExecutionId);
|
||||
Assert.Equal(original.ParentExecutionId, roundTripped.ParentExecutionId);
|
||||
Assert.Equal(original.SourceSiteId, roundTripped.SourceSiteId);
|
||||
Assert.Equal(original.SourceNode, roundTripped.SourceNode);
|
||||
Assert.Equal(original.SourceInstanceId, roundTripped.SourceInstanceId);
|
||||
Assert.Equal(original.SourceScript, roundTripped.SourceScript);
|
||||
Assert.Equal(original.Actor, roundTripped.Actor);
|
||||
@@ -99,6 +101,7 @@ public class AuditEventDtoMapperTests
|
||||
Assert.Equal(string.Empty, dto.ExecutionId);
|
||||
Assert.Equal(string.Empty, dto.ParentExecutionId);
|
||||
Assert.Equal(string.Empty, dto.SourceSiteId);
|
||||
Assert.Equal(string.Empty, dto.SourceNode);
|
||||
Assert.Equal(string.Empty, dto.SourceInstanceId);
|
||||
Assert.Equal(string.Empty, dto.SourceScript);
|
||||
Assert.Equal(string.Empty, dto.Actor);
|
||||
@@ -124,6 +127,7 @@ public class AuditEventDtoMapperTests
|
||||
ExecutionId = string.Empty,
|
||||
ParentExecutionId = string.Empty,
|
||||
SourceSiteId = string.Empty,
|
||||
SourceNode = string.Empty,
|
||||
SourceInstanceId = string.Empty,
|
||||
SourceScript = string.Empty,
|
||||
Actor = string.Empty,
|
||||
@@ -141,6 +145,7 @@ public class AuditEventDtoMapperTests
|
||||
Assert.Null(evt.ExecutionId);
|
||||
Assert.Null(evt.ParentExecutionId);
|
||||
Assert.Null(evt.SourceSiteId);
|
||||
Assert.Null(evt.SourceNode);
|
||||
Assert.Null(evt.SourceInstanceId);
|
||||
Assert.Null(evt.SourceScript);
|
||||
Assert.Null(evt.Actor);
|
||||
@@ -232,4 +237,52 @@ public class AuditEventDtoMapperTests
|
||||
Assert.Equal("ApiCallCached", dto.Kind);
|
||||
Assert.Equal("Parked", dto.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuditEventDto_round_trip_preserves_SourceNode()
|
||||
{
|
||||
var evt = new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = DateTime.UtcNow,
|
||||
Channel = AuditChannel.ApiOutbound,
|
||||
Kind = AuditKind.ApiCall,
|
||||
Status = AuditStatus.Delivered,
|
||||
SourceNode = "node-a"
|
||||
};
|
||||
|
||||
var dto = AuditEventDtoMapper.ToDto(evt);
|
||||
|
||||
// Wire form: empty-string-means-null convention; populated value
|
||||
// travels verbatim.
|
||||
Assert.Equal("node-a", dto.SourceNode);
|
||||
|
||||
var roundTripped = AuditEventDtoMapper.FromDto(dto);
|
||||
|
||||
Assert.Equal("node-a", roundTripped.SourceNode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuditEventDto_round_trip_preserves_null_SourceNode()
|
||||
{
|
||||
var evt = new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = DateTime.UtcNow,
|
||||
Channel = AuditChannel.ApiOutbound,
|
||||
Kind = AuditKind.ApiCall,
|
||||
Status = AuditStatus.Delivered,
|
||||
SourceNode = null
|
||||
};
|
||||
|
||||
var dto = AuditEventDtoMapper.ToDto(evt);
|
||||
|
||||
// ToDto collapses null → empty on the wire…
|
||||
Assert.Equal(string.Empty, dto.SourceNode);
|
||||
|
||||
var roundTripped = AuditEventDtoMapper.FromDto(dto);
|
||||
|
||||
// …and FromDto rehydrates empty → null.
|
||||
Assert.Null(roundTripped.SourceNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public class AuditEventProtoTests
|
||||
Kind = "ApiCall",
|
||||
CorrelationId = Guid.NewGuid().ToString(),
|
||||
SourceSiteId = "site-1",
|
||||
SourceNode = "node-a",
|
||||
SourceInstanceId = "Pump01",
|
||||
SourceScript = "OnDemand",
|
||||
Actor = "design-key",
|
||||
@@ -49,6 +50,7 @@ public class AuditEventProtoTests
|
||||
Assert.Equal(original.Kind, deserialized.Kind);
|
||||
Assert.Equal(original.CorrelationId, deserialized.CorrelationId);
|
||||
Assert.Equal(original.SourceSiteId, deserialized.SourceSiteId);
|
||||
Assert.Equal(original.SourceNode, deserialized.SourceNode);
|
||||
Assert.Equal(original.SourceInstanceId, deserialized.SourceInstanceId);
|
||||
Assert.Equal(original.SourceScript, deserialized.SourceScript);
|
||||
Assert.Equal(original.Actor, deserialized.Actor);
|
||||
|
||||
@@ -39,6 +39,7 @@ public class CachedTelemetryProtoTests
|
||||
Channel = "ApiOutbound",
|
||||
Target = "ERP.GetOrder",
|
||||
SourceSite = "site-melbourne",
|
||||
SourceNode = "node-a",
|
||||
Status = "Delivered",
|
||||
RetryCount = 3,
|
||||
LastError = "transient 503",
|
||||
@@ -55,6 +56,7 @@ public class CachedTelemetryProtoTests
|
||||
Assert.Equal(original.Channel, deserialized.Channel);
|
||||
Assert.Equal(original.Target, deserialized.Target);
|
||||
Assert.Equal(original.SourceSite, deserialized.SourceSite);
|
||||
Assert.Equal(original.SourceNode, deserialized.SourceNode);
|
||||
Assert.Equal(original.Status, deserialized.Status);
|
||||
Assert.Equal(original.RetryCount, deserialized.RetryCount);
|
||||
Assert.Equal(original.LastError, deserialized.LastError);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using ScadaLink.Communication.Grpc;
|
||||
|
||||
@@ -28,6 +29,7 @@ public class SiteCallDtoMapperTests
|
||||
Channel = "ApiOutbound",
|
||||
Target = "ERP.GetOrder",
|
||||
SourceSite = "site-melbourne",
|
||||
SourceNode = "node-a",
|
||||
Status = "Delivered",
|
||||
RetryCount = 3,
|
||||
LastError = "transient 503",
|
||||
@@ -43,6 +45,7 @@ public class SiteCallDtoMapperTests
|
||||
Assert.Equal("ApiOutbound", entity.Channel);
|
||||
Assert.Equal("ERP.GetOrder", entity.Target);
|
||||
Assert.Equal("site-melbourne", entity.SourceSite);
|
||||
Assert.Equal("node-a", entity.SourceNode);
|
||||
Assert.Equal("Delivered", entity.Status);
|
||||
Assert.Equal(3, entity.RetryCount);
|
||||
Assert.Equal("transient 503", entity.LastError);
|
||||
@@ -121,6 +124,40 @@ public class SiteCallDtoMapperTests
|
||||
Assert.Throws<ArgumentNullException>(() => SiteCallDtoMapper.FromDto(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SiteCallOperationalDto_round_trip_preserves_SourceNode()
|
||||
{
|
||||
// Populated SourceNode travels verbatim across the wire and through
|
||||
// the DTO→entity mapper.
|
||||
var dto = NewMinimalDto();
|
||||
dto.SourceNode = "node-a";
|
||||
|
||||
var bytes = dto.ToByteArray();
|
||||
var onWire = SiteCallOperationalDto.Parser.ParseFrom(bytes);
|
||||
Assert.Equal("node-a", onWire.SourceNode);
|
||||
|
||||
var entity = SiteCallDtoMapper.FromDto(onWire);
|
||||
|
||||
Assert.Equal("node-a", entity.SourceNode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SiteCallOperationalDto_round_trip_preserves_null_SourceNode()
|
||||
{
|
||||
// The DTO uses an empty-string-means-null convention on the wire;
|
||||
// FromDto rehydrates that back to a true null on the entity.
|
||||
var dto = NewMinimalDto();
|
||||
// SourceNode left at proto default (empty string) — semantically null.
|
||||
|
||||
var bytes = dto.ToByteArray();
|
||||
var onWire = SiteCallOperationalDto.Parser.ParseFrom(bytes);
|
||||
Assert.Equal(string.Empty, onWire.SourceNode);
|
||||
|
||||
var entity = SiteCallDtoMapper.FromDto(onWire);
|
||||
|
||||
Assert.Null(entity.SourceNode);
|
||||
}
|
||||
|
||||
private static SiteCallOperationalDto NewMinimalDto() => new()
|
||||
{
|
||||
TrackedOperationId = Guid.NewGuid().ToString(),
|
||||
|
||||
Reference in New Issue
Block a user