diff --git a/src/ScadaLink.AuditLog/Central/CentralAuditWriter.cs b/src/ScadaLink.AuditLog/Central/CentralAuditWriter.cs index 545215c..9f07968 100644 --- a/src/ScadaLink.AuditLog/Central/CentralAuditWriter.cs +++ b/src/ScadaLink.AuditLog/Central/CentralAuditWriter.cs @@ -136,6 +136,13 @@ public sealed class CentralAuditWriter : ICentralAuditWriter // misbehaving custom counter does, swallowing here keeps the // best-effort contract intact. } + // Log the input event's identifying fields. These three (EventId, + // Kind, Status) are immutable across the filter+stamp chain — the + // `with` clones above touch only SourceNode and IngestedAtUtc — so + // referencing `evt` here is intentional and equivalent to the + // stamped record for diagnostics. If you add a field here that the + // stamp chain DOES mutate (e.g., SourceNode), reference the latest + // post-stamp record name instead, not `evt`. _logger.LogWarning( ex, "CentralAuditWriter failed for EventId {EventId} (Kind={Kind}, Status={Status})", diff --git a/tests/ScadaLink.AuditLog.Tests/Central/CentralAuditWriterTests.cs b/tests/ScadaLink.AuditLog.Tests/Central/CentralAuditWriterTests.cs index cb37e8e..3901f06 100644 --- a/tests/ScadaLink.AuditLog.Tests/Central/CentralAuditWriterTests.cs +++ b/tests/ScadaLink.AuditLog.Tests/Central/CentralAuditWriterTests.cs @@ -180,4 +180,34 @@ public class CentralAuditWriterTests Arg.Is(e => e.SourceNode == null), Arg.Any()); } + + [Fact] + public async Task WriteAsync_PassesThroughCallerSourceNode_WhenNoProviderInjected() + { + // Locks the back-compat contract for the optional `nodeIdentity = null` + // ctor parameter: when no provider is wired (e.g. legacy M4 test + // composition roots), the writer must not stamp — caller value passes + // through unmodified. Distinct code path from + // "provider supplied, returns null", which the test above covers. + var (writer, repo) = BuildWriterWithIdentity(nodeIdentity: null); + + await writer.WriteAsync(NewEvent() with { SourceNode = "node-z" }); + + await repo.Received(1).InsertIfNotExistsAsync( + Arg.Is(e => e.SourceNode == "node-z"), + Arg.Any()); + } + + [Fact] + public async Task WriteAsync_LeavesSourceNodeNull_WhenNoProviderInjected() + { + // Same back-compat contract for the null-caller-null-provider case. + var (writer, repo) = BuildWriterWithIdentity(nodeIdentity: null); + + await writer.WriteAsync(NewEvent()); + + await repo.Received(1).InsertIfNotExistsAsync( + Arg.Is(e => e.SourceNode == null), + Arg.Any()); + } }