feat(flow-debugger): outbound calls on the External lane — CLI projection, specs, rig demo + verification step I
CLI flow-debug table now projects through FlowGraphBuilder (one row per step, outbound lifecycles folded, new 'lifecycle' column; per-event fallback kept). Specs: Component-CentralUI (four lanes, node anatomy, pending refresh), Component-AuditLog (central method-script Database.* now audited; replay reads the three outbound channels), Component-InboundAPI (DbOutbound row per helper call), Component-ScriptAnalysis (REQ-SA-7 kinds + Async edges), design doc v1.1 status line, CLAUDE.md bullet, deferred-work register row 111 (Retry/ Discard + ?correlationId= deep links deferred). Rig: seed-flow-outbound-demo.sh (RigRest/RigDb/FlowDemo + OutboundDemo script + FlowOutboundDemo method/key) and verify-delmia-notifier.sh step I (live rows inline, replay until terminal). Rig verification result follows in its own commit. Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -4,6 +4,7 @@ using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.CLI.Commands;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CLI.Tests;
|
||||
@@ -181,4 +182,58 @@ public class FlowDebugCommandsTests
|
||||
Assert.Equal("""{"error":"x"}""", FlowDebugTableProjection.ProjectEvents("""{"error":"x"}"""));
|
||||
Assert.Equal("[1]", FlowDebugTableProjection.ProjectEvents("[1]"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Projection_FoldsAnOutboundLifecycle_IntoOneExternalLaneRow()
|
||||
{
|
||||
var root = Guid.NewGuid();
|
||||
var child = Guid.NewGuid();
|
||||
var corr = Guid.NewGuid();
|
||||
var t0 = new DateTime(2026, 9, 6, 9, 0, 0, 0, DateTimeKind.Utc);
|
||||
var events = new List<AuditEvent>
|
||||
{
|
||||
ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.RouteCall, AuditStatus.Delivered,
|
||||
occurredAtUtc: t0, target: "DelmiaSim01.OutboundDemo", executionId: root, durationMs: 300,
|
||||
extra: new ScriptTraceExtra { ChildExecutionId = child }.ToJson()),
|
||||
ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
|
||||
occurredAtUtc: t0.AddMilliseconds(10), target: "OutboundDemo", executionId: child, parentExecutionId: root,
|
||||
sourceSiteId: "site-a", sourceInstanceId: "DelmiaSim01", durationMs: 250),
|
||||
ScadaBridgeAuditEventFactory.Create(AuditChannel.ApiOutbound, AuditKind.ApiCall, AuditStatus.Delivered,
|
||||
occurredAtUtc: t0.AddMilliseconds(20), target: "RigRest.Echo", executionId: child, parentExecutionId: root,
|
||||
sourceSiteId: "site-a", sourceInstanceId: "DelmiaSim01", durationMs: 40),
|
||||
ScadaBridgeAuditEventFactory.Create(AuditChannel.DbOutbound, AuditKind.CachedSubmit, AuditStatus.Submitted,
|
||||
occurredAtUtc: t0.AddMilliseconds(70), target: "RigDb", correlationId: corr, executionId: child, parentExecutionId: root,
|
||||
sourceSiteId: "site-a", sourceInstanceId: "DelmiaSim01"),
|
||||
ScadaBridgeAuditEventFactory.Create(AuditChannel.DbOutbound, AuditKind.DbWriteCached, AuditStatus.Attempted,
|
||||
occurredAtUtc: t0.AddMilliseconds(90), target: "RigDb", correlationId: corr, executionId: child, parentExecutionId: root,
|
||||
sourceSiteId: "site-a", sourceInstanceId: "DelmiaSim01", durationMs: 15),
|
||||
ScadaBridgeAuditEventFactory.Create(AuditChannel.DbOutbound, AuditKind.CachedResolve, AuditStatus.Delivered,
|
||||
occurredAtUtc: t0.AddMilliseconds(120), target: "RigDb", correlationId: corr, executionId: child, parentExecutionId: root,
|
||||
sourceSiteId: "site-a", sourceInstanceId: "DelmiaSim01"),
|
||||
};
|
||||
var result = new FlowDebugRunResult(root, true, null, null, events, false, new DateTimeOffset(t0), new DateTimeOffset(t0.AddSeconds(1)));
|
||||
|
||||
var projected = FlowDebugTableProjection.ProjectEvents(JsonSerializer.Serialize(result, CamelCase));
|
||||
|
||||
using var doc = JsonDocument.Parse(projected);
|
||||
var rows = doc.RootElement.EnumerateArray().ToList();
|
||||
Assert.Equal(4, rows.Count); // route, run, sync api call, ONE folded db write
|
||||
|
||||
Assert.Equal("ApiCall", rows[2].GetProperty("kind").GetString());
|
||||
Assert.Equal("External", rows[2].GetProperty("lane").GetString());
|
||||
Assert.Equal("RigRest.Echo", rows[2].GetProperty("target").GetString());
|
||||
Assert.Equal("1 row", rows[2].GetProperty("lifecycle").GetString());
|
||||
Assert.Equal(40, rows[2].GetProperty("durationMs").GetInt32());
|
||||
|
||||
Assert.Equal("DbCall", rows[3].GetProperty("kind").GetString());
|
||||
Assert.Equal("External", rows[3].GetProperty("lane").GetString());
|
||||
Assert.Equal("RigDb", rows[3].GetProperty("target").GetString());
|
||||
Assert.Equal("3 rows", rows[3].GetProperty("lifecycle").GetString());
|
||||
Assert.Equal("Delivered", rows[3].GetProperty("status").GetString());
|
||||
Assert.Equal(50, rows[3].GetProperty("durationMs").GetInt32());
|
||||
Assert.Equal("09:00:00.070", rows[3].GetProperty("occurred").GetString());
|
||||
|
||||
Assert.Equal("DelmiaSim01.OutboundDemo", rows[1].GetProperty("target").GetString());
|
||||
Assert.Equal(JsonValueKind.Null, rows[1].GetProperty("lifecycle").ValueKind);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user