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:
Joseph Doherty
2026-05-23 16:10:03 -04:00
parent 990eb02fe0
commit dfaa416ebe
9 changed files with 221 additions and 40 deletions

View File

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