feat(audit)!: ScadaBridge C3 — swap to canonical ZB.MOM.WW.Audit.AuditEvent across seams/emitters/DTO/redactor wiring; transitional 24-col storage shim (Task 2.5)

This commit is contained in:
Joseph Doherty
2026-06-02 12:37:50 -04:00
parent 5aaf9e2923
commit db707bb0de
127 changed files with 2240 additions and 3886 deletions
@@ -1,10 +1,11 @@
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.AuditLog.Site;
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.TestSupport;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Site;
@@ -51,18 +52,23 @@ public class SqliteAuditWriterWriteTests
return connection;
}
private static AuditEvent NewEvent(Guid? id = null, DateTime? occurredAtUtc = null)
{
return new AuditEvent
{
EventId = id ?? Guid.NewGuid(),
OccurredAtUtc = occurredAtUtc ?? DateTime.UtcNow,
Channel = AuditChannel.ApiOutbound,
Kind = AuditKind.ApiCall,
Status = AuditStatus.Delivered,
PayloadTruncated = false,
};
}
// C3 (Task 2.5): build the canonical ZB.MOM.WW.Audit.AuditEvent via the shared
// factory. The SQLite writer's transitional shim decomposes it into the 24 columns
// (defaulting ForwardState=Pending) on INSERT and recomposes the canonical record
// on read. ExecutionId/SourceNode ride through DetailsJson / the top-level field.
private static AuditEvent NewEvent(
Guid? id = null,
DateTime? occurredAtUtc = null,
Guid? executionId = null,
string? sourceNode = null)
=> ScadaBridgeAuditEventFactory.Create(
channel: AuditChannel.ApiOutbound,
kind: AuditKind.ApiCall,
status: AuditStatus.Delivered,
eventId: id ?? Guid.NewGuid(),
occurredAtUtc: occurredAtUtc ?? DateTime.UtcNow,
executionId: executionId,
sourceNode: sourceNode);
[Fact]
public async Task WriteAsync_FreshEvent_PersistsWithForwardStatePending()
@@ -134,7 +140,10 @@ public class SqliteAuditWriterWriteTests
var (writer, dataSource) = CreateWriter(nameof(WriteAsync_ForcesForwardStatePending_IfNull));
await using var _ = writer;
var evt = NewEvent() with { ForwardState = null };
// C3 (Task 2.5): ForwardState is no longer a field on the canonical record;
// a fresh canonical event carries none, and the SQLite shim defaults it to
// Pending on INSERT — exactly the behaviour this test pins.
var evt = NewEvent();
await writer.WriteAsync(evt);
using var connection = OpenVerifierConnection(dataSource);
@@ -372,13 +381,13 @@ public class SqliteAuditWriterWriteTests
await using var _w = writer;
var executionId = Guid.NewGuid();
var evt = NewEvent() with { ExecutionId = executionId };
var evt = NewEvent(executionId: executionId);
await writer.WriteAsync(evt);
var rows = await writer.ReadPendingAsync(limit: 10);
var row = Assert.Single(rows);
Assert.Equal(executionId, row.ExecutionId);
Assert.Equal(executionId, row.AsRow().ExecutionId);
}
[Fact]
@@ -387,13 +396,13 @@ public class SqliteAuditWriterWriteTests
var (writer, _) = CreateWriter(nameof(WriteAsync_NullExecutionId_RoundTripsAsNull));
await using var _w = writer;
var evt = NewEvent() with { ExecutionId = null };
var evt = NewEvent(); // executionId defaults to null
await writer.WriteAsync(evt);
var rows = await writer.ReadPendingAsync(limit: 10);
var row = Assert.Single(rows);
Assert.Null(row.ExecutionId);
Assert.Null(row.AsRow().ExecutionId);
}
// ----- SourceNode stamping (Tasks 11/12) ----- //
@@ -425,7 +434,7 @@ public class SqliteAuditWriterWriteTests
// Reconciled rows from another node arrive with their origin's
// SourceNode already populated; the writer must preserve it.
var evt = NewEvent() with { SourceNode = "node-z" };
var evt = NewEvent(sourceNode: "node-z");
await writer.WriteAsync(evt);
var rows = await writer.ReadPendingAsync(limit: 10);