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:
+3
-1
@@ -11,7 +11,9 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NSubstitute;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using IAuditWriter = ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services.IAuditWriter;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
|
||||
+17
-17
@@ -1,7 +1,10 @@
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Entities;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
@@ -53,20 +56,17 @@ public class CachedCallCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMigr
|
||||
private static CachedCallTelemetry SubmitPacket(
|
||||
TrackedOperationId id, string siteId, DateTime nowUtc, string target = "ERP.GetOrder") =>
|
||||
new(
|
||||
Audit: new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = nowUtc,
|
||||
Channel = AuditChannel.ApiOutbound,
|
||||
Kind = AuditKind.CachedSubmit,
|
||||
CorrelationId = id.Value,
|
||||
SourceSiteId = siteId,
|
||||
SourceInstanceId = "Plant.Pump42",
|
||||
SourceScript = "ScriptActor:doStuff",
|
||||
Target = target,
|
||||
Status = AuditStatus.Submitted,
|
||||
ForwardState = AuditForwardState.Pending,
|
||||
},
|
||||
Audit: ScadaBridgeAuditEventFactory.Create(
|
||||
eventId: Guid.NewGuid(),
|
||||
occurredAtUtc: nowUtc,
|
||||
channel: AuditChannel.ApiOutbound,
|
||||
kind: AuditKind.CachedSubmit,
|
||||
correlationId: id.Value,
|
||||
sourceSiteId: siteId,
|
||||
sourceInstanceId: "Plant.Pump42",
|
||||
sourceScript: "ScriptActor:doStuff",
|
||||
target: target,
|
||||
status: AuditStatus.Submitted),
|
||||
Operational: new SiteCallOperational(
|
||||
TrackedOperationId: id,
|
||||
Channel: "ApiOutbound",
|
||||
@@ -149,7 +149,7 @@ public class CachedCallCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMigr
|
||||
// 1 Submit + 2 transient Attempted + 1 terminal Attempted + 1
|
||||
// CachedResolve = 5 audit rows. The plan allows 4-5; this is the
|
||||
// happy path emitting exactly 5.
|
||||
var auditRows = await read.Set<AuditEvent>()
|
||||
var auditRows = await read.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId)
|
||||
.ToListAsync();
|
||||
Assert.InRange(auditRows.Count, 4, 5);
|
||||
@@ -215,7 +215,7 @@ public class CachedCallCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMigr
|
||||
Assert.NotNull(siteCall.TerminalAtUtc);
|
||||
|
||||
// Terminal audit row should also be Parked.
|
||||
var resolve = await read.Set<AuditEvent>()
|
||||
var resolve = await read.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId && e.Kind == AuditKind.CachedResolve)
|
||||
.SingleAsync();
|
||||
Assert.Equal(AuditStatus.Parked, resolve.Status);
|
||||
@@ -255,7 +255,7 @@ public class CachedCallCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMigr
|
||||
Assert.NotNull(siteCall.TerminalAtUtc);
|
||||
|
||||
// 1 Submit + 1 Attempted + 1 CachedResolve = 3 audit rows.
|
||||
var auditRows = await read.Set<AuditEvent>()
|
||||
var auditRows = await read.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId)
|
||||
.ToListAsync();
|
||||
Assert.Equal(3, auditRows.Count);
|
||||
|
||||
+16
-16
@@ -1,7 +1,10 @@
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Entities;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
@@ -42,20 +45,17 @@ public class CachedWriteCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMig
|
||||
private static CachedCallTelemetry DbSubmitPacket(
|
||||
TrackedOperationId id, string siteId, DateTime nowUtc, string target = "OperationsDb.UpdateOrder") =>
|
||||
new(
|
||||
Audit: new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = nowUtc,
|
||||
Channel = AuditChannel.DbOutbound,
|
||||
Kind = AuditKind.CachedSubmit,
|
||||
CorrelationId = id.Value,
|
||||
SourceSiteId = siteId,
|
||||
SourceInstanceId = "Plant.Pump42",
|
||||
SourceScript = "ScriptActor:doStuff",
|
||||
Target = target,
|
||||
Status = AuditStatus.Submitted,
|
||||
ForwardState = AuditForwardState.Pending,
|
||||
},
|
||||
Audit: ScadaBridgeAuditEventFactory.Create(
|
||||
eventId: Guid.NewGuid(),
|
||||
occurredAtUtc: nowUtc,
|
||||
channel: AuditChannel.DbOutbound,
|
||||
kind: AuditKind.CachedSubmit,
|
||||
correlationId: id.Value,
|
||||
sourceSiteId: siteId,
|
||||
sourceInstanceId: "Plant.Pump42",
|
||||
sourceScript: "ScriptActor:doStuff",
|
||||
target: target,
|
||||
status: AuditStatus.Submitted),
|
||||
Operational: new SiteCallOperational(
|
||||
TrackedOperationId: id,
|
||||
Channel: "DbOutbound",
|
||||
@@ -122,7 +122,7 @@ public class CachedWriteCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMig
|
||||
Assert.Equal(0, siteCall.RetryCount);
|
||||
Assert.NotNull(siteCall.TerminalAtUtc);
|
||||
|
||||
var auditRows = await read.Set<AuditEvent>()
|
||||
var auditRows = await read.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId)
|
||||
.ToListAsync();
|
||||
Assert.Equal(3, auditRows.Count);
|
||||
@@ -182,7 +182,7 @@ public class CachedWriteCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMig
|
||||
Assert.Equal("Parked", siteCall.Status);
|
||||
Assert.NotNull(siteCall.TerminalAtUtc);
|
||||
|
||||
var resolve = await read.Set<AuditEvent>()
|
||||
var resolve = await read.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId && e.Kind == AuditKind.CachedResolve)
|
||||
.SingleAsync();
|
||||
Assert.Equal(AuditStatus.Parked, resolve.Status);
|
||||
|
||||
+16
-16
@@ -1,7 +1,10 @@
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Entities;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
@@ -54,20 +57,17 @@ public class CombinedTelemetryIdempotencyTests : TestKit, IClassFixture<MsSqlMig
|
||||
{
|
||||
var dto = new CachedTelemetryPacket
|
||||
{
|
||||
AuditEvent = AuditEventDtoMapper.ToDto(new AuditEvent
|
||||
{
|
||||
EventId = eventId,
|
||||
OccurredAtUtc = nowUtc,
|
||||
Channel = AuditChannel.ApiOutbound,
|
||||
Kind = kind,
|
||||
CorrelationId = trackedId.Value,
|
||||
SourceSiteId = siteId,
|
||||
Target = "ERP.GetOrder",
|
||||
Status = auditStatus,
|
||||
HttpStatus = httpStatus,
|
||||
ErrorMessage = lastError,
|
||||
ForwardState = AuditForwardState.Pending,
|
||||
}),
|
||||
AuditEvent = AuditEventDtoMapper.ToDto(ScadaBridgeAuditEventFactory.Create(
|
||||
eventId: eventId,
|
||||
occurredAtUtc: nowUtc,
|
||||
channel: AuditChannel.ApiOutbound,
|
||||
kind: kind,
|
||||
correlationId: trackedId.Value,
|
||||
sourceSiteId: siteId,
|
||||
target: "ERP.GetOrder",
|
||||
status: auditStatus,
|
||||
httpStatus: httpStatus,
|
||||
errorMessage: lastError)),
|
||||
Operational = new SiteCallOperationalDto
|
||||
{
|
||||
TrackedOperationId = trackedId.Value.ToString("D"),
|
||||
@@ -131,7 +131,7 @@ public class CombinedTelemetryIdempotencyTests : TestKit, IClassFixture<MsSqlMig
|
||||
await using var read = harness.CreateReadContext();
|
||||
|
||||
// AuditLog: exactly ONE row for the EventId (insert-if-not-exists).
|
||||
var auditCount = await read.Set<AuditEvent>()
|
||||
var auditCount = await read.Set<AuditLogRow>()
|
||||
.CountAsync(e => e.EventId == eventId);
|
||||
Assert.Equal(1, auditCount);
|
||||
|
||||
@@ -183,7 +183,7 @@ public class CombinedTelemetryIdempotencyTests : TestKit, IClassFixture<MsSqlMig
|
||||
// AuditLog: TWO rows now exist for this lifecycle — the Submit and
|
||||
// the Attempted. Their order is by OccurredAtUtc; the test doesn't
|
||||
// assert ordering, only count + correlation.
|
||||
var auditRows = await read.Set<AuditEvent>()
|
||||
var auditRows = await read.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId)
|
||||
.ToListAsync();
|
||||
Assert.Equal(2, auditRows.Count);
|
||||
|
||||
+17
-17
@@ -220,17 +220,17 @@ public class DatabaseSyncEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMig
|
||||
new AuditLogQueryFilter(SourceSiteIds: new[] { siteId }),
|
||||
new AuditLogPaging(PageSize: 10));
|
||||
var evt = Assert.Single(rows);
|
||||
Assert.Equal(AuditChannel.DbOutbound, evt.Channel);
|
||||
Assert.Equal(AuditKind.DbWrite, evt.Kind);
|
||||
Assert.Equal(AuditStatus.Delivered, evt.Status);
|
||||
Assert.Equal(siteId, evt.SourceSiteId);
|
||||
Assert.Equal(InstanceName, evt.SourceInstanceId);
|
||||
Assert.Equal(SourceScript, evt.SourceScript);
|
||||
Assert.NotNull(evt.Extra);
|
||||
Assert.Contains("\"op\":\"write\"", evt.Extra);
|
||||
Assert.Contains("\"rowsAffected\":1", evt.Extra);
|
||||
Assert.Equal(AuditChannel.DbOutbound, evt.AsRow().Channel);
|
||||
Assert.Equal(AuditKind.DbWrite, evt.AsRow().Kind);
|
||||
Assert.Equal(AuditStatus.Delivered, evt.AsRow().Status);
|
||||
Assert.Equal(siteId, evt.AsRow().SourceSiteId);
|
||||
Assert.Equal(InstanceName, evt.AsRow().SourceInstanceId);
|
||||
Assert.Equal(SourceScript, evt.AsRow().SourceScript);
|
||||
Assert.NotNull(evt.AsRow().Extra);
|
||||
Assert.Contains("\"op\":\"write\"", evt.AsRow().Extra);
|
||||
Assert.Contains("\"rowsAffected\":1", evt.AsRow().Extra);
|
||||
// Central stamps IngestedAtUtc; the site never sets it.
|
||||
Assert.NotNull(evt.IngestedAtUtc);
|
||||
Assert.NotNull(evt.AsRow().IngestedAtUtc);
|
||||
Assert.StartsWith(ConnectionName, evt.Target);
|
||||
}, TimeSpan.FromSeconds(15));
|
||||
}
|
||||
@@ -288,13 +288,13 @@ public class DatabaseSyncEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMig
|
||||
new AuditLogQueryFilter(SourceSiteIds: new[] { siteId }),
|
||||
new AuditLogPaging(PageSize: 10));
|
||||
var evt = Assert.Single(rows);
|
||||
Assert.Equal(AuditChannel.DbOutbound, evt.Channel);
|
||||
Assert.Equal(AuditKind.DbWrite, evt.Kind);
|
||||
Assert.Equal(AuditStatus.Delivered, evt.Status);
|
||||
Assert.NotNull(evt.Extra);
|
||||
Assert.Contains("\"op\":\"read\"", evt.Extra);
|
||||
Assert.Contains("\"rowsReturned\":2", evt.Extra);
|
||||
Assert.NotNull(evt.IngestedAtUtc);
|
||||
Assert.Equal(AuditChannel.DbOutbound, evt.AsRow().Channel);
|
||||
Assert.Equal(AuditKind.DbWrite, evt.AsRow().Kind);
|
||||
Assert.Equal(AuditStatus.Delivered, evt.AsRow().Status);
|
||||
Assert.NotNull(evt.AsRow().Extra);
|
||||
Assert.Contains("\"op\":\"read\"", evt.AsRow().Extra);
|
||||
Assert.Contains("\"rowsReturned\":2", evt.AsRow().Extra);
|
||||
Assert.NotNull(evt.AsRow().IngestedAtUtc);
|
||||
}, TimeSpan.FromSeconds(15));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -219,18 +219,18 @@ public class ExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMigration
|
||||
// core promise of the per-run correlation value.
|
||||
Assert.All(rows, r =>
|
||||
{
|
||||
Assert.NotNull(r.ExecutionId);
|
||||
Assert.Equal(executionId, r.ExecutionId);
|
||||
Assert.Equal(siteId, r.SourceSiteId);
|
||||
Assert.NotNull(r.AsRow().ExecutionId);
|
||||
Assert.Equal(executionId, r.AsRow().ExecutionId);
|
||||
Assert.Equal(siteId, r.AsRow().SourceSiteId);
|
||||
// Central stamps IngestedAtUtc; the site never sets it.
|
||||
Assert.NotNull(r.IngestedAtUtc);
|
||||
Assert.NotNull(r.AsRow().IngestedAtUtc);
|
||||
});
|
||||
|
||||
// The two rows are the two distinct trust-boundary actions — one
|
||||
// outbound API call and one outbound DB write — proving the shared
|
||||
// id spans different channels, not two rows of the same action.
|
||||
Assert.Single(rows, r => r.Channel == AuditChannel.ApiOutbound && r.Kind == AuditKind.ApiCall);
|
||||
Assert.Single(rows, r => r.Channel == AuditChannel.DbOutbound && r.Kind == AuditKind.DbWrite);
|
||||
Assert.Single(rows, r => r.AsRow().Channel == AuditChannel.ApiOutbound && r.AsRow().Kind == AuditKind.ApiCall);
|
||||
Assert.Single(rows, r => r.AsRow().Channel == AuditChannel.DbOutbound && r.AsRow().Kind == AuditKind.DbWrite);
|
||||
}, TimeSpan.FromSeconds(15));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
@@ -223,15 +223,13 @@ public class InboundApiAuditTests : IClassFixture<MsSqlMigrationFixture>
|
||||
Assert.Equal(System.Net.HttpStatusCode.OK, resp.StatusCode);
|
||||
|
||||
var evt = await AwaitOneAsync(methodName);
|
||||
Assert.Equal(AuditChannel.ApiInbound, evt.Channel);
|
||||
Assert.Equal(AuditKind.InboundRequest, evt.Kind);
|
||||
Assert.Equal(AuditStatus.Delivered, evt.Status);
|
||||
Assert.Equal(200, evt.HttpStatus);
|
||||
Assert.Equal(AuditChannel.ApiInbound, evt.AsRow().Channel);
|
||||
Assert.Equal(AuditKind.InboundRequest, evt.AsRow().Kind);
|
||||
Assert.Equal(AuditStatus.Delivered, evt.AsRow().Status);
|
||||
Assert.Equal(200, evt.AsRow().HttpStatus);
|
||||
Assert.Equal("integration-svc", evt.Actor);
|
||||
// Central direct-write — no site-local forward state (alog.md §6).
|
||||
Assert.Null(evt.ForwardState);
|
||||
// IngestedAtUtc stamped by the central writer.
|
||||
Assert.NotNull(evt.IngestedAtUtc);
|
||||
Assert.NotNull(evt.AsRow().IngestedAtUtc);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
@@ -257,13 +255,15 @@ public class InboundApiAuditTests : IClassFixture<MsSqlMigrationFixture>
|
||||
Assert.Equal(System.Net.HttpStatusCode.Unauthorized, resp.StatusCode);
|
||||
|
||||
var evt = await AwaitOneAsync(methodName);
|
||||
Assert.Equal(AuditChannel.ApiInbound, evt.Channel);
|
||||
Assert.Equal(AuditKind.InboundAuthFailure, evt.Kind);
|
||||
Assert.Equal(AuditStatus.Failed, evt.Status);
|
||||
Assert.Equal(401, evt.HttpStatus);
|
||||
Assert.Equal(AuditChannel.ApiInbound, evt.AsRow().Channel);
|
||||
Assert.Equal(AuditKind.InboundAuthFailure, evt.AsRow().Kind);
|
||||
Assert.Equal(AuditStatus.Failed, evt.AsRow().Status);
|
||||
Assert.Equal(401, evt.AsRow().HttpStatus);
|
||||
// Never echo back an unauthenticated principal — middleware suppresses
|
||||
// the framework user resolution on 401/403 paths.
|
||||
Assert.Null(evt.Actor);
|
||||
// the framework user resolution on 401/403 paths. C3 (Task 2.5): the
|
||||
// canonical Actor is a non-null string (empty when absent); the row view
|
||||
// maps empty → null, preserving the "no principal" assertion.
|
||||
Assert.Null(evt.AsRow().Actor);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
@@ -290,10 +290,10 @@ public class InboundApiAuditTests : IClassFixture<MsSqlMigrationFixture>
|
||||
Assert.Equal(System.Net.HttpStatusCode.InternalServerError, resp.StatusCode);
|
||||
|
||||
var evt = await AwaitOneAsync(methodName);
|
||||
Assert.Equal(AuditChannel.ApiInbound, evt.Channel);
|
||||
Assert.Equal(AuditKind.InboundRequest, evt.Kind);
|
||||
Assert.Equal(AuditStatus.Failed, evt.Status);
|
||||
Assert.Equal(500, evt.HttpStatus);
|
||||
Assert.Equal(AuditChannel.ApiInbound, evt.AsRow().Channel);
|
||||
Assert.Equal(AuditKind.InboundRequest, evt.AsRow().Kind);
|
||||
Assert.Equal(AuditStatus.Failed, evt.AsRow().Status);
|
||||
Assert.Equal(500, evt.AsRow().HttpStatus);
|
||||
Assert.Equal("integration-svc", evt.Actor);
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -1,6 +1,8 @@
|
||||
using Akka.Actor;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
|
||||
+19
-22
@@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
@@ -185,21 +185,18 @@ public class NotifyDispatcherAuditTrailTests : TestKit, IClassFixture<MsSqlMigra
|
||||
{
|
||||
await using var ctx = CreateContext();
|
||||
var repo = new AuditLogRepository(ctx);
|
||||
var submitEvt = new AuditEvent
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = DateTime.UtcNow.AddMinutes(-1),
|
||||
Channel = AuditChannel.Notification,
|
||||
Kind = AuditKind.NotifySend,
|
||||
CorrelationId = notificationId,
|
||||
SourceSiteId = siteId,
|
||||
SourceInstanceId = "Plant.Pump42",
|
||||
SourceScript = "AlarmScript",
|
||||
Target = "ops-team",
|
||||
Status = AuditStatus.Submitted,
|
||||
ForwardState = AuditForwardState.Forwarded,
|
||||
IngestedAtUtc = DateTime.UtcNow.AddMinutes(-1),
|
||||
};
|
||||
var submitEvt = ScadaBridgeAuditEventFactory.Create(
|
||||
eventId: Guid.NewGuid(),
|
||||
occurredAtUtc: DateTime.UtcNow.AddMinutes(-1),
|
||||
channel: AuditChannel.Notification,
|
||||
kind: AuditKind.NotifySend,
|
||||
correlationId: notificationId,
|
||||
sourceSiteId: siteId,
|
||||
sourceInstanceId: "Plant.Pump42",
|
||||
sourceScript: "AlarmScript",
|
||||
target: "ops-team",
|
||||
status: AuditStatus.Submitted,
|
||||
ingestedAtUtc: new DateTimeOffset(DateTime.UtcNow.AddMinutes(-1)));
|
||||
await repo.InsertIfNotExistsAsync(submitEvt);
|
||||
}
|
||||
|
||||
@@ -248,9 +245,9 @@ public class NotifyDispatcherAuditTrailTests : TestKit, IClassFixture<MsSqlMigra
|
||||
new AuditLogPaging(PageSize: 50));
|
||||
// 1 Submit + 1 Attempted = 2 rows so far.
|
||||
Assert.Equal(2, rows.Count);
|
||||
Assert.Single(rows, r => r.Kind == AuditKind.NotifyDeliver
|
||||
&& r.Status == AuditStatus.Attempted);
|
||||
Assert.Single(rows, r => r.Kind == AuditKind.NotifySend);
|
||||
Assert.Single(rows, r => r.AsRow().Kind == AuditKind.NotifyDeliver
|
||||
&& r.AsRow().Status == AuditStatus.Attempted);
|
||||
Assert.Single(rows, r => r.AsRow().Kind == AuditKind.NotifySend);
|
||||
}, TimeSpan.FromSeconds(15));
|
||||
|
||||
// Second tick: success → second Attempted + one Delivered terminal.
|
||||
@@ -265,10 +262,10 @@ public class NotifyDispatcherAuditTrailTests : TestKit, IClassFixture<MsSqlMigra
|
||||
// 1 Submit + 2 Attempted + 1 Delivered terminal = 4 rows.
|
||||
Assert.InRange(rows.Count, 3, 4);
|
||||
var notifyDeliverRows = rows
|
||||
.Where(r => r.Kind == AuditKind.NotifyDeliver)
|
||||
.Where(r => r.AsRow().Kind == AuditKind.NotifyDeliver)
|
||||
.ToList();
|
||||
Assert.Equal(2, notifyDeliverRows.Count(r => r.Status == AuditStatus.Attempted));
|
||||
var terminal = Assert.Single(notifyDeliverRows, r => r.Status == AuditStatus.Delivered);
|
||||
Assert.Equal(2, notifyDeliverRows.Count(r => r.AsRow().Status == AuditStatus.Attempted));
|
||||
var terminal = Assert.Single(notifyDeliverRows, r => r.AsRow().Status == AuditStatus.Delivered);
|
||||
// All NotifyDeliver rows correlate to the original notification id.
|
||||
Assert.All(notifyDeliverRows, r => Assert.Equal(notificationId, r.CorrelationId));
|
||||
Assert.Equal("ops-team", terminal.Target);
|
||||
|
||||
+15
-15
@@ -7,7 +7,9 @@ using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
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.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Entities;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
|
||||
@@ -129,16 +131,14 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
new(new DbContextOptionsBuilder<ScadaBridgeDbContext>()
|
||||
.UseSqlServer(_fixture.ConnectionString).Options);
|
||||
|
||||
private static AuditEvent NewEvent(string siteId, DateTime occurredAt) => new()
|
||||
{
|
||||
EventId = Guid.NewGuid(),
|
||||
OccurredAtUtc = occurredAt,
|
||||
Channel = AuditChannel.ApiOutbound,
|
||||
Kind = AuditKind.ApiCall,
|
||||
Status = AuditStatus.Delivered,
|
||||
SourceSiteId = siteId,
|
||||
Target = "external-system-a/method",
|
||||
};
|
||||
private static AuditEvent NewEvent(string siteId, DateTime occurredAt) => ScadaBridgeAuditEventFactory.Create(
|
||||
eventId: Guid.NewGuid(),
|
||||
occurredAtUtc: occurredAt,
|
||||
channel: AuditChannel.ApiOutbound,
|
||||
kind: AuditKind.ApiCall,
|
||||
status: AuditStatus.Delivered,
|
||||
sourceSiteId: siteId,
|
||||
target: "external-system-a/method");
|
||||
|
||||
private SqliteAuditWriter CreateInMemorySqliteWriter() =>
|
||||
new SqliteAuditWriter(
|
||||
@@ -243,7 +243,7 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
await AwaitAssertAsync(async () =>
|
||||
{
|
||||
await using var ctx = CreateContext();
|
||||
var count = await ctx.Set<AuditEvent>()
|
||||
var count = await ctx.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId)
|
||||
.CountAsync();
|
||||
Assert.Equal(totalEvents, count);
|
||||
@@ -265,7 +265,7 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
// Step 5: assert no duplicates by EventId — central must have
|
||||
// exactly the 200 rows we wrote at the site (one row per EventId).
|
||||
await using var verify = CreateContext();
|
||||
var centralIds = await verify.Set<AuditEvent>()
|
||||
var centralIds = await verify.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId)
|
||||
.Select(e => e.EventId)
|
||||
.ToListAsync();
|
||||
@@ -317,7 +317,7 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
await AwaitAssertAsync(async () =>
|
||||
{
|
||||
await using var ctx = CreateContext();
|
||||
var count = await ctx.Set<AuditEvent>()
|
||||
var count = await ctx.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId)
|
||||
.CountAsync();
|
||||
Assert.Equal(totalEvents, count);
|
||||
@@ -339,7 +339,7 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
// even though the cursor + read-Reconciled-too semantics could
|
||||
// theoretically re-fetch on the second cycle.
|
||||
await using var verify = CreateContext();
|
||||
var rows = await verify.Set<AuditEvent>()
|
||||
var rows = await verify.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId)
|
||||
.ToListAsync();
|
||||
Assert.Equal(totalEvents, rows.Count);
|
||||
|
||||
+15
-14
@@ -17,7 +17,8 @@ using ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.TestSupport;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using IAuditWriter = ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services.IAuditWriter;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi;
|
||||
@@ -315,23 +316,23 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
// + NotifyDeliver Attempted/Delivered (2) = 7 rows for the routed run.
|
||||
Assert.True(siteRows.Count == 7,
|
||||
"Expected 7 routed-run audit rows; saw: "
|
||||
+ string.Join(", ", siteRows.Select(r => $"{r.Channel}/{r.Kind}/{r.Status}")));
|
||||
Assert.Single(siteRows, r => r.Channel == AuditChannel.ApiOutbound && r.Kind == AuditKind.ApiCall);
|
||||
Assert.Single(siteRows, r => r.Kind == AuditKind.CachedSubmit);
|
||||
Assert.Single(siteRows, r => r.Kind == AuditKind.CachedResolve);
|
||||
Assert.Single(siteRows, r => r.Kind == AuditKind.NotifySend);
|
||||
Assert.Equal(2, siteRows.Count(r => r.Kind == AuditKind.NotifyDeliver));
|
||||
+ string.Join(", ", siteRows.Select(r => $"{r.AsRow().Channel}/{r.AsRow().Kind}/{r.AsRow().Status}")));
|
||||
Assert.Single(siteRows, r => r.AsRow().Channel == AuditChannel.ApiOutbound && r.AsRow().Kind == AuditKind.ApiCall);
|
||||
Assert.Single(siteRows, r => r.AsRow().Kind == AuditKind.CachedSubmit);
|
||||
Assert.Single(siteRows, r => r.AsRow().Kind == AuditKind.CachedResolve);
|
||||
Assert.Single(siteRows, r => r.AsRow().Kind == AuditKind.NotifySend);
|
||||
Assert.Equal(2, siteRows.Count(r => r.AsRow().Kind == AuditKind.NotifyDeliver));
|
||||
|
||||
// CORE PROMISE: every routed-run row carries the SAME non-null
|
||||
// ParentExecutionId — the inbound request's ExecutionId.
|
||||
var parentIds = siteRows.Select(r => r.ParentExecutionId).Distinct().ToList();
|
||||
var parentIds = siteRows.Select(r => r.AsRow().ParentExecutionId).Distinct().ToList();
|
||||
Assert.Single(parentIds);
|
||||
Assert.NotNull(parentIds[0]);
|
||||
var inboundExecutionId = parentIds[0]!.Value;
|
||||
|
||||
// The routed run has its OWN distinct ExecutionId — not the parent's.
|
||||
var routedExecutionIds = siteRows
|
||||
.Select(r => r.ExecutionId)
|
||||
.Select(r => r.AsRow().ExecutionId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
Assert.Single(routedExecutionIds);
|
||||
@@ -345,9 +346,9 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
new AuditLogQueryFilter(ExecutionId: inboundExecutionId),
|
||||
new AuditLogPaging(PageSize: 10));
|
||||
var inboundRow = Assert.Single(inboundRows,
|
||||
r => r.Channel == AuditChannel.ApiInbound && r.Kind == AuditKind.InboundRequest);
|
||||
Assert.Equal(AuditStatus.Delivered, inboundRow.Status);
|
||||
Assert.Null(inboundRow.ParentExecutionId);
|
||||
r => r.AsRow().Channel == AuditChannel.ApiInbound && r.AsRow().Kind == AuditKind.InboundRequest);
|
||||
Assert.Equal(AuditStatus.Delivered, inboundRow.AsRow().Status);
|
||||
Assert.Null(inboundRow.AsRow().ParentExecutionId);
|
||||
|
||||
// The parentExecutionId filter pulls the routed run's complete
|
||||
// trust-boundary footprint (all 7 routed rows, none of the inbound).
|
||||
@@ -355,7 +356,7 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
new AuditLogQueryFilter(ParentExecutionId: inboundExecutionId),
|
||||
new AuditLogPaging(PageSize: 100));
|
||||
Assert.Equal(7, byParent.Count);
|
||||
Assert.All(byParent, r => Assert.Equal(routedExecutionId, r.ExecutionId));
|
||||
Assert.All(byParent, r => Assert.Equal(routedExecutionId, r.AsRow().ExecutionId));
|
||||
|
||||
// GetExecutionTreeAsync returns BOTH executions in one chain —
|
||||
// inbound (root) and routed (child), regardless of entry point.
|
||||
@@ -502,7 +503,7 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
var pendingCached = await sqliteWriter.ReadPendingCachedTelemetryAsync(256);
|
||||
var forwarded = await sqliteWriter.ReadForwardedAsync(256);
|
||||
var kinds = pending.Concat(pendingCached).Concat(forwarded)
|
||||
.Select(r => r.Kind).ToHashSet();
|
||||
.Select(r => r.AsRow().Kind).ToHashSet();
|
||||
var missing = expectedKinds.Where(k => !kinds.Contains(k)).ToList();
|
||||
Assert.True(
|
||||
missing.Count == 0,
|
||||
|
||||
@@ -7,7 +7,9 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Configuration;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Entities;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
|
||||
@@ -201,7 +203,7 @@ WHERE name = 'UX_AuditLog_EventId'
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(500));
|
||||
|
||||
await using var verify = CreateContext();
|
||||
var rows = await verify.Set<AuditEvent>()
|
||||
var rows = await verify.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == siteId)
|
||||
.ToListAsync();
|
||||
|
||||
@@ -325,16 +327,14 @@ WHERE name = 'UX_AuditLog_EventId'
|
||||
var freshEventId = Guid.NewGuid();
|
||||
var freshOccurred = new DateTime(2026, 5, 15, 12, 0, 0, DateTimeKind.Utc);
|
||||
var freshSite = "purge-idem-fresh-" + Guid.NewGuid().ToString("N").Substring(0, 8);
|
||||
var freshEvt = new AuditEvent
|
||||
{
|
||||
EventId = freshEventId,
|
||||
OccurredAtUtc = freshOccurred,
|
||||
Channel = AuditChannel.ApiOutbound,
|
||||
Kind = AuditKind.ApiCall,
|
||||
Status = AuditStatus.Delivered,
|
||||
SourceSiteId = freshSite,
|
||||
Target = "system-x/method",
|
||||
};
|
||||
var freshEvt = ScadaBridgeAuditEventFactory.Create(
|
||||
eventId: freshEventId,
|
||||
occurredAtUtc: freshOccurred,
|
||||
channel: AuditChannel.ApiOutbound,
|
||||
kind: AuditKind.ApiCall,
|
||||
status: AuditStatus.Delivered,
|
||||
sourceSiteId: freshSite,
|
||||
target: "system-x/method");
|
||||
|
||||
await using (var ctx = CreateContext())
|
||||
{
|
||||
@@ -345,7 +345,7 @@ WHERE name = 'UX_AuditLog_EventId'
|
||||
}
|
||||
|
||||
await using var verify = CreateContext();
|
||||
var rows = await verify.Set<AuditEvent>()
|
||||
var rows = await verify.Set<AuditLogRow>()
|
||||
.Where(e => e.SourceSiteId == freshSite)
|
||||
.ToListAsync();
|
||||
Assert.Single(rows);
|
||||
|
||||
+10
-12
@@ -8,7 +8,7 @@ using ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.TestSupport;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
@@ -67,16 +67,14 @@ public class SyncCallEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMigrati
|
||||
return new ScadaBridgeDbContext(options);
|
||||
}
|
||||
|
||||
private static AuditEvent NewEvent(string siteId, Guid? id = null) => new()
|
||||
{
|
||||
EventId = id ?? Guid.NewGuid(),
|
||||
OccurredAtUtc = DateTime.UtcNow,
|
||||
Channel = AuditChannel.ApiOutbound,
|
||||
Kind = AuditKind.ApiCall,
|
||||
Status = AuditStatus.Delivered,
|
||||
SourceSiteId = siteId,
|
||||
Target = "external-system-a/method",
|
||||
};
|
||||
private static AuditEvent NewEvent(string siteId, Guid? id = null) => ScadaBridgeAuditEventFactory.Create(
|
||||
eventId: id ?? Guid.NewGuid(),
|
||||
occurredAtUtc: DateTime.UtcNow,
|
||||
channel: AuditChannel.ApiOutbound,
|
||||
kind: AuditKind.ApiCall,
|
||||
status: AuditStatus.Delivered,
|
||||
sourceSiteId: siteId,
|
||||
target: "external-system-a/method");
|
||||
|
||||
private static IOptions<SqliteAuditWriterOptions> InMemorySqliteOptions() =>
|
||||
Options.Create(new SqliteAuditWriterOptions
|
||||
@@ -167,7 +165,7 @@ public class SyncCallEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMigrati
|
||||
Assert.Single(rows);
|
||||
Assert.Equal(evt.EventId, rows[0].EventId);
|
||||
// Central stamps IngestedAtUtc; site never sets it.
|
||||
Assert.NotNull(rows[0].IngestedAtUtc);
|
||||
Assert.NotNull(rows[0].AsRow().IngestedAtUtc);
|
||||
}, TimeSpan.FromSeconds(15));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user