feat(audit): M5.2 per-node stuck-count KPIs (T6) — repo per-node aggregation, actor message pair, CentralUI tiles
This commit is contained in:
+48
@@ -497,6 +497,54 @@ public class SiteCallAuditRepositoryTests : IClassFixture<MsSqlMigrationFixture>
|
||||
Assert.Null(b.OldestPendingAge);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task ComputePerNodeKpisAsync_ScopesCountsToEachNode()
|
||||
{
|
||||
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
|
||||
|
||||
// Use unique site + node combos to isolate from other tests running
|
||||
// concurrently on the shared MsSql fixture.
|
||||
var nodeId = "node-b3-" + Guid.NewGuid().ToString("N").Substring(0, 8);
|
||||
var nodeB = nodeId + "-b";
|
||||
await using var context = CreateContext();
|
||||
var repo = new SiteCallAuditRepository(context);
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var stuckCutoff = now.AddMinutes(-10);
|
||||
var intervalSince = now.AddHours(-1);
|
||||
|
||||
// nodeId: 2 buffered (one stuck), 1 parked.
|
||||
await repo.UpsertAsync(NewRow(TrackedOperationId.New(), status: "Attempted",
|
||||
createdAtUtc: now.AddMinutes(-30), sourceNode: nodeId));
|
||||
await repo.UpsertAsync(NewRow(TrackedOperationId.New(), status: "Attempted",
|
||||
createdAtUtc: now.AddMinutes(-2), sourceNode: nodeId));
|
||||
await repo.UpsertAsync(NewRow(TrackedOperationId.New(), status: "Parked",
|
||||
createdAtUtc: now.AddMinutes(-5), terminal: true, sourceNode: nodeId));
|
||||
// nodeB: 1 delivered within interval only.
|
||||
await repo.UpsertAsync(NewRow(TrackedOperationId.New(), status: "Delivered",
|
||||
createdAtUtc: now.AddMinutes(-4), updatedAtUtc: now.AddMinutes(-1),
|
||||
terminal: true, terminalAtUtc: now.AddMinutes(-1), sourceNode: nodeB));
|
||||
// Null SourceNode row — must NOT appear in per-node results.
|
||||
await repo.UpsertAsync(NewRow(TrackedOperationId.New(), status: "Attempted",
|
||||
createdAtUtc: now.AddMinutes(-3), sourceNode: null));
|
||||
|
||||
var perNode = await repo.ComputePerNodeKpisAsync(stuckCutoff, intervalSince);
|
||||
|
||||
var na = Assert.Single(perNode, n => n.SourceNode == nodeId);
|
||||
Assert.Equal(2, na.BufferedCount);
|
||||
Assert.Equal(1, na.ParkedCount);
|
||||
Assert.Equal(1, na.StuckCount);
|
||||
Assert.NotNull(na.OldestPendingAge);
|
||||
|
||||
var nb = Assert.Single(perNode, n => n.SourceNode == nodeB);
|
||||
Assert.Equal(0, nb.BufferedCount);
|
||||
Assert.Equal(1, nb.DeliveredLastInterval);
|
||||
Assert.Null(nb.OldestPendingAge);
|
||||
|
||||
// Null-node row must be absent.
|
||||
Assert.DoesNotContain(perNode, n => n.SourceNode is null);
|
||||
}
|
||||
|
||||
// --- helpers ------------------------------------------------------------
|
||||
|
||||
private ScadaBridgeDbContext CreateContext()
|
||||
|
||||
Reference in New Issue
Block a user