29129ef17a
Fold rule: outbound rows sharing (kind, CorrelationId) become one FlowStep with a Lifecycle list — first row's identity/time/input, latest row's outcome/ subtitle/output/error, DurationMs = sync row's own or submit→terminal wall time (null while pending). Chain skips async steps (Notify, or first row CachedSubmit) and links them with Async edges from the preceding chain step. Spawner edges from a non-RouteCall are Spawn: true. The Response links from the last non-async step of the inbound execution (the RouteCall), not the globally last cascaded step (D9). FlowGraphView places a Response at max column + 1 so the D9 origin does not pull it left (pulled forward from Task 7 to keep the layout test green). Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
367 lines
20 KiB
C#
367 lines
20 KiB
C#
using ZB.MOM.WW.Audit;
|
|
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.Commons.Tests.Debugging;
|
|
|
|
/// <summary>
|
|
/// Flow Debugger design 2026-09-05 §7.2: lane/edge/boundary rules over a Delmia-shaped
|
|
/// event list, truncation pass-through, and total-ness on unknown kinds.
|
|
/// </summary>
|
|
public class FlowGraphBuilderTests
|
|
{
|
|
private static readonly DateTime T0 = new(2026, 9, 5, 12, 0, 0, DateTimeKind.Utc);
|
|
private static readonly Guid Root = Guid.NewGuid();
|
|
private static readonly Guid SiteRun = Guid.NewGuid();
|
|
private static readonly Guid Cascade = Guid.NewGuid();
|
|
|
|
/// <summary>Inbound → RouteCall → site ScriptRun → write → wait → write, plus a value-change ScriptRun spawned by the second write.</summary>
|
|
private static List<AuditEvent> DelmiaShaped()
|
|
{
|
|
var inbound = ScadaBridgeAuditEventFactory.Create(AuditChannel.ApiInbound, AuditKind.InboundRequest, AuditStatus.Delivered,
|
|
occurredAtUtc: T0, actor: "delmia-key", target: "DelmiaRecipeDownload", sourceNode: "central-a",
|
|
executionId: Root, durationMs: 900, requestSummary: "{\"MachineCode\":\"M1\"}", responseSummary: "{\"ok\":true}");
|
|
var route = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.RouteCall, AuditStatus.Delivered,
|
|
occurredAtUtc: T0.AddMilliseconds(10), actor: "inbound-api", target: "M1.ProcessRecipeDownload", sourceNode: "central-a",
|
|
executionId: Root, durationMs: 850, extra: new ScriptTraceExtra { ChildExecutionId = SiteRun }.ToJson());
|
|
var run = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
|
|
occurredAtUtc: T0.AddMilliseconds(20), actor: "ProcessRecipeDownload", target: "ProcessRecipeDownload", sourceNode: "node-a",
|
|
executionId: SiteRun, parentExecutionId: Root, sourceSiteId: "site-a", sourceInstanceId: "M1", durationMs: 800,
|
|
extra: new ScriptTraceExtra { TriggerKind = "call" }.ToJson());
|
|
var write1 = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.AttributeWrite, AuditStatus.Delivered,
|
|
occurredAtUtc: T0.AddMilliseconds(30), actor: "ProcessRecipeDownload", target: "DelmiaReceiver.RecipePath", sourceNode: "node-a",
|
|
executionId: SiteRun, parentExecutionId: Root, sourceSiteId: "site-a", sourceInstanceId: "M1", durationMs: 5,
|
|
extra: new ScriptTraceExtra { Writes = [new AttributeWriteDetail("DelmiaReceiver.RecipePath", "C:\\r.nc", true, null)] }.ToJson());
|
|
var wait = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.AttributeWait, AuditStatus.Delivered,
|
|
occurredAtUtc: T0.AddMilliseconds(40), actor: "ProcessRecipeDownload", target: "DelmiaReceiver.Processed", sourceNode: "node-a",
|
|
executionId: SiteRun, parentExecutionId: Root, sourceSiteId: "site-a", sourceInstanceId: "M1", durationMs: 700,
|
|
extra: new ScriptTraceExtra { WaitAttribute = "DelmiaReceiver.Processed", WaitOutcome = "satisfied", ObservedValue = "true" }.ToJson());
|
|
var write2 = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.AttributeWrite, AuditStatus.Delivered,
|
|
occurredAtUtc: T0.AddMilliseconds(750), actor: "ProcessRecipeDownload", target: "DelmiaReceiver.Trigger", sourceNode: "node-a",
|
|
executionId: SiteRun, parentExecutionId: Root, sourceSiteId: "site-a", sourceInstanceId: "M1", durationMs: 3,
|
|
extra: new ScriptTraceExtra { Writes = [new AttributeWriteDetail("DelmiaReceiver.Trigger", "false", true, null)] }.ToJson());
|
|
var cascade = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
|
|
occurredAtUtc: T0.AddMilliseconds(760), actor: "OnTriggerReset", target: "OnTriggerReset", sourceNode: "node-a",
|
|
executionId: Cascade, parentExecutionId: SiteRun, sourceSiteId: "site-a", sourceInstanceId: "M1", durationMs: 10,
|
|
extra: new ScriptTraceExtra { TriggerKind = "valuechange", TriggerAttribute = "DelmiaReceiver.Trigger", TriggerValue = "false" }.ToJson());
|
|
|
|
// Deliberately shuffled: the builder must order by time.
|
|
return [wait, cascade, inbound, write2, run, route, write1];
|
|
}
|
|
|
|
[Fact]
|
|
public void DelmiaShape_ProjectsLanes_Edges_AndSynthesizedResponse()
|
|
{
|
|
var graph = FlowGraphBuilder.Build(DelmiaShaped());
|
|
|
|
Assert.False(graph.Predicted);
|
|
Assert.False(graph.Truncated);
|
|
Assert.Equal(8, graph.Steps.Count); // 7 events + synthesized Response
|
|
|
|
var kinds = graph.Steps.Select(s => s.Kind).ToArray();
|
|
Assert.Equal(
|
|
new[]
|
|
{
|
|
FlowStepKind.InboundRequest, FlowStepKind.RouteCall, FlowStepKind.ScriptRun, FlowStepKind.AttributeWrite,
|
|
FlowStepKind.AttributeWait, FlowStepKind.AttributeWrite, FlowStepKind.ScriptRun, FlowStepKind.Response,
|
|
},
|
|
kinds);
|
|
|
|
Assert.Equal(FlowLane.Central, graph.Steps[0].Lane);
|
|
Assert.Equal(FlowLane.Central, graph.Steps[1].Lane);
|
|
Assert.Equal(FlowLane.Site, graph.Steps[2].Lane);
|
|
Assert.Equal(FlowLane.Tags, graph.Steps[3].Lane);
|
|
Assert.Equal(FlowLane.Site, graph.Steps[4].Lane);
|
|
Assert.Equal(FlowLane.Tags, graph.Steps[5].Lane);
|
|
Assert.Equal(FlowLane.Site, graph.Steps[6].Lane);
|
|
Assert.Equal(FlowLane.Central, graph.Steps[7].Lane);
|
|
|
|
// Exactly one boundary crossing: route → site run. The Response links from the RouteCall (D9), Central → Central.
|
|
var crossing = Assert.Single(graph.Edges, e => e.CrossesBoundary);
|
|
Assert.Equal(graph.Steps[1].EventId, crossing.FromEventId);
|
|
Assert.Equal(graph.Steps[2].EventId, crossing.ToEventId);
|
|
Assert.Contains(graph.Edges, e => e.FromEventId == graph.Steps[1].EventId && e.ToEventId == graph.Steps[7].EventId && !e.CrossesBoundary);
|
|
Assert.DoesNotContain(graph.Edges, e => e.FromEventId == graph.Steps[6].EventId && e.ToEventId == graph.Steps[7].EventId);
|
|
|
|
// One inferred echo edge: first write → satisfied wait.
|
|
var external = Assert.Single(graph.Edges, e => e.External);
|
|
Assert.Equal(graph.Steps[3].EventId, external.FromEventId);
|
|
Assert.Equal(graph.Steps[4].EventId, external.ToEventId);
|
|
|
|
// The cascade run hangs off the write that spawned it (same site, no boundary) — a Spawn edge (D9);
|
|
// the route → run edge and every in-chain edge are not.
|
|
var spawn = Assert.Single(graph.Edges, e => e.Spawn);
|
|
Assert.Equal(graph.Steps[5].EventId, spawn.FromEventId);
|
|
Assert.Equal(graph.Steps[6].EventId, spawn.ToEventId);
|
|
Assert.False(spawn.CrossesBoundary);
|
|
Assert.Contains(graph.Edges, e => e.FromEventId == graph.Steps[1].EventId && e.ToEventId == graph.Steps[2].EventId && !e.Spawn);
|
|
Assert.DoesNotContain(graph.Edges, e => e.Async);
|
|
|
|
// Inbound → route call chained inside the root execution.
|
|
Assert.Contains(graph.Edges, e => e.FromEventId == graph.Steps[0].EventId && e.ToEventId == graph.Steps[1].EventId);
|
|
|
|
// Projection detail.
|
|
Assert.Equal("→ " + SiteRun.ToString("D")[..8], graph.Steps[1].Subtitle);
|
|
Assert.Equal("M1 · call", graph.Steps[2].Subtitle);
|
|
Assert.Equal("DelmiaReceiver.RecipePath = C:\\r.nc", graph.Steps[3].Subtitle);
|
|
Assert.Equal("satisfied · true", graph.Steps[4].Subtitle);
|
|
Assert.Equal("{\"ok\":true}", graph.Steps[7].OutputJson);
|
|
Assert.All(graph.Steps, s => Assert.Equal(FlowStepOutcome.Ok, s.Outcome));
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_IsDeterministic_ResponseIdIsStable()
|
|
{
|
|
var events = DelmiaShaped();
|
|
var a = FlowGraphBuilder.Build(events);
|
|
var b = FlowGraphBuilder.Build(events);
|
|
Assert.Equal(a.Steps.Select(s => s.EventId), b.Steps.Select(s => s.EventId));
|
|
Assert.Equal(a.Edges, b.Edges);
|
|
}
|
|
|
|
[Fact]
|
|
public void Truncated_PassesThrough()
|
|
{
|
|
Assert.True(FlowGraphBuilder.Build(DelmiaShaped(), truncated: true).Truncated);
|
|
Assert.True(FlowGraphBuilder.Build(Array.Empty<AuditEvent>(), truncated: true).Truncated);
|
|
Assert.Empty(FlowGraphBuilder.Build(Array.Empty<AuditEvent>()).Steps);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnknownKind_ProjectsToUnknown_NeverThrows()
|
|
{
|
|
var weird = new AuditEvent
|
|
{
|
|
EventId = Guid.NewGuid(),
|
|
OccurredAtUtc = T0,
|
|
Actor = "x",
|
|
Action = "Future.Thing",
|
|
Outcome = AuditOutcome.Success,
|
|
DetailsJson = "{\"channel\":\"Future\",\"kind\":\"Thing\",\"extra\":\"not json at all\"}",
|
|
};
|
|
var garbage = new AuditEvent
|
|
{
|
|
EventId = Guid.NewGuid(),
|
|
OccurredAtUtc = T0,
|
|
Actor = "x",
|
|
Action = "?",
|
|
Outcome = AuditOutcome.Failure,
|
|
DetailsJson = "{{{",
|
|
};
|
|
|
|
var graph = FlowGraphBuilder.Build([weird, garbage]);
|
|
|
|
Assert.Equal(2, graph.Steps.Count);
|
|
Assert.All(graph.Steps, s => Assert.Equal(FlowStepKind.Unknown, s.Kind));
|
|
Assert.All(graph.Steps, s => Assert.Null(s.Extra));
|
|
Assert.Empty(graph.Edges);
|
|
}
|
|
|
|
[Fact]
|
|
public void FailedScriptRun_WithTimeoutMessage_IsTimedOut()
|
|
{
|
|
var run = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Failed,
|
|
occurredAtUtc: T0, target: "S", executionId: SiteRun, errorMessage: "Script timed out after 30s");
|
|
var write = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.AttributeWrite, AuditStatus.Failed,
|
|
occurredAtUtc: T0.AddMilliseconds(1), target: "A", executionId: SiteRun, errorMessage: "rejected");
|
|
|
|
var graph = FlowGraphBuilder.Build([run, write]);
|
|
|
|
Assert.Equal(FlowStepOutcome.TimedOut, graph.Steps[0].Outcome);
|
|
Assert.Equal(FlowStepOutcome.Failed, graph.Steps[1].Outcome);
|
|
Assert.Single(graph.Edges);
|
|
}
|
|
|
|
[Fact]
|
|
public void ScriptTraceExtra_RoundTrips_CamelCase_AndIgnoresUnknownMembers()
|
|
{
|
|
var extra = new ScriptTraceExtra { ChildExecutionId = SiteRun, WaitTimeoutMs = 1500, Source = "flow-debugger" };
|
|
var json = extra.ToJson();
|
|
Assert.Contains("\"childExecutionId\"", json);
|
|
Assert.DoesNotContain("triggerKind", json);
|
|
|
|
var back = ScriptTraceExtra.TryParse(json.Insert(1, "\"futureField\":1,"));
|
|
Assert.Equal(extra, back);
|
|
Assert.Null(ScriptTraceExtra.TryParse(null));
|
|
Assert.Null(ScriptTraceExtra.TryParse("nope"));
|
|
}
|
|
|
|
// ---- Outbound calls (2026-09-06 plan, Task 2) --------------------------------------------
|
|
|
|
private static AuditEvent SiteRunRow(int ms, Guid executionId = default) => ScadaBridgeAuditEventFactory.Create(
|
|
AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
|
|
occurredAtUtc: T0.AddMilliseconds(ms), actor: "S", target: "S", sourceNode: "node-a",
|
|
executionId: executionId == default ? SiteRun : executionId, sourceSiteId: "site-a", sourceInstanceId: "M1", durationMs: 800,
|
|
extra: new ScriptTraceExtra { TriggerKind = "call" }.ToJson());
|
|
|
|
private static AuditEvent WriteRow(int ms, string attr) => ScadaBridgeAuditEventFactory.Create(
|
|
AuditChannel.ScriptTrace, AuditKind.AttributeWrite, AuditStatus.Delivered,
|
|
occurredAtUtc: T0.AddMilliseconds(ms), actor: "S", target: attr, sourceNode: "node-a",
|
|
executionId: SiteRun, sourceSiteId: "site-a", sourceInstanceId: "M1", durationMs: 2,
|
|
extra: new ScriptTraceExtra { Writes = [new AttributeWriteDetail(attr, "1", true, null)] }.ToJson());
|
|
|
|
private static AuditEvent Outbound(AuditChannel channel, AuditKind kind, AuditStatus status, int ms, Guid? correlation, string target,
|
|
string node = "node-a", string? error = null, int? durationMs = null, string? request = null, string? response = null)
|
|
=> ScadaBridgeAuditEventFactory.Create(channel, kind, status,
|
|
occurredAtUtc: T0.AddMilliseconds(ms), actor: "S", target: target, sourceNode: node, correlationId: correlation,
|
|
executionId: SiteRun, sourceSiteId: "site-a", sourceInstanceId: "M1", errorMessage: error, durationMs: durationMs,
|
|
requestSummary: request, responseSummary: response);
|
|
|
|
[Fact]
|
|
public void CachedApiCall_FourRows_FoldIntoOneAsyncNode()
|
|
{
|
|
var corr = Guid.NewGuid();
|
|
var events = new List<AuditEvent>
|
|
{
|
|
SiteRunRow(20),
|
|
WriteRow(30, "A"),
|
|
Outbound(AuditChannel.ApiOutbound, AuditKind.CachedSubmit, AuditStatus.Submitted, 35, corr, "Delmia.AckRecipe", request: "{\"id\":1}"),
|
|
WriteRow(40, "B"),
|
|
Outbound(AuditChannel.ApiOutbound, AuditKind.ApiCallCached, AuditStatus.Forwarded, 2000, corr, "Delmia.AckRecipe"),
|
|
Outbound(AuditChannel.ApiOutbound, AuditKind.ApiCallCached, AuditStatus.Attempted, 3000, corr, "Delmia.AckRecipe", error: "503", durationMs: 40),
|
|
Outbound(AuditChannel.ApiOutbound, AuditKind.CachedResolve, AuditStatus.Delivered, 4135, corr, "Delmia.AckRecipe", response: "{\"ok\":true}"),
|
|
};
|
|
|
|
var graph = FlowGraphBuilder.Build(events);
|
|
|
|
Assert.Equal(4, graph.Steps.Count);
|
|
var api = Assert.Single(graph.Steps, s => s.Kind == FlowStepKind.ApiCall);
|
|
Assert.Equal(FlowLane.External, api.Lane);
|
|
Assert.Equal(events[2].EventId, api.EventId);
|
|
Assert.Equal(T0.AddMilliseconds(35), api.OccurredAtUtc.UtcDateTime);
|
|
Assert.Equal(4, api.Lifecycle!.Count);
|
|
Assert.Equal(new[] { "CachedSubmit", "ApiCallCached", "ApiCallCached", "CachedResolve" }, api.Lifecycle.Select(r => r.Kind));
|
|
Assert.Equal("503", api.Lifecycle[2].ErrorMessage);
|
|
Assert.Equal(FlowStepOutcome.Ok, api.Outcome);
|
|
Assert.Equal(4100, api.DurationMs);
|
|
Assert.Equal("{\"id\":1}", api.InputJson);
|
|
Assert.Equal("{\"ok\":true}", api.OutputJson);
|
|
Assert.Equal("Delivered · cached", api.Subtitle);
|
|
Assert.Null(api.ErrorMessage);
|
|
Assert.Equal(SiteRun, api.ExecutionId);
|
|
|
|
var run = graph.Steps[0]; var w1 = graph.Steps[1]; var w2 = graph.Steps[3];
|
|
Assert.Equal(new[] { FlowStepKind.ScriptRun, FlowStepKind.AttributeWrite, FlowStepKind.ApiCall, FlowStepKind.AttributeWrite },
|
|
graph.Steps.Select(s => s.Kind));
|
|
var async = Assert.Single(graph.Edges, e => e.Async);
|
|
Assert.Equal(w1.EventId, async.FromEventId);
|
|
Assert.Equal(api.EventId, async.ToEventId);
|
|
Assert.Contains(graph.Edges, e => e.FromEventId == run.EventId && e.ToEventId == w1.EventId && !e.Async);
|
|
Assert.Contains(graph.Edges, e => e.FromEventId == w1.EventId && e.ToEventId == w2.EventId && !e.Async);
|
|
Assert.DoesNotContain(graph.Edges, e => e.FromEventId == api.EventId);
|
|
Assert.Equal(3, graph.Edges.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void SyncApiCall_SitsInChain_NoAsyncEdge()
|
|
{
|
|
var events = new List<AuditEvent>
|
|
{
|
|
SiteRunRow(20),
|
|
WriteRow(30, "A"),
|
|
Outbound(AuditChannel.ApiOutbound, AuditKind.ApiCall, AuditStatus.Delivered, 35, null, "Delmia.AckRecipe", durationMs: 212),
|
|
WriteRow(40, "B"),
|
|
};
|
|
|
|
var graph = FlowGraphBuilder.Build(events);
|
|
|
|
var api = Assert.Single(graph.Steps, s => s.Kind == FlowStepKind.ApiCall);
|
|
Assert.Equal(212, api.DurationMs);
|
|
Assert.Single(api.Lifecycle!);
|
|
Assert.Equal("Delivered · sync", api.Subtitle);
|
|
Assert.DoesNotContain(graph.Edges, e => e.Async);
|
|
Assert.Contains(graph.Edges, e => e.FromEventId == graph.Steps[1].EventId && e.ToEventId == api.EventId);
|
|
Assert.Contains(graph.Edges, e => e.FromEventId == api.EventId && e.ToEventId == graph.Steps[3].EventId);
|
|
Assert.DoesNotContain(graph.Edges, e => e.FromEventId == graph.Steps[1].EventId && e.ToEventId == graph.Steps[3].EventId);
|
|
}
|
|
|
|
[Fact]
|
|
public void TwoSyncCalls_NullCorrelation_AreNotFoldedTogether()
|
|
{
|
|
var events = new List<AuditEvent>
|
|
{
|
|
SiteRunRow(20),
|
|
Outbound(AuditChannel.DbOutbound, AuditKind.DbWrite, AuditStatus.Delivered, 30, null, "MesDb"),
|
|
Outbound(AuditChannel.DbOutbound, AuditKind.DbWrite, AuditStatus.Failed, 40, null, "MesDb", error: "deadlock"),
|
|
};
|
|
|
|
var graph = FlowGraphBuilder.Build(events);
|
|
|
|
Assert.Equal(2, graph.Steps.Count(s => s.Kind == FlowStepKind.DbCall));
|
|
Assert.Equal(FlowStepOutcome.Failed, graph.Steps[2].Outcome);
|
|
Assert.Equal(2, graph.Edges.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void NotifySend_PlusCentralDeliverParked_FoldsIntoOneParkedNotifyNode()
|
|
{
|
|
var notificationId = Guid.NewGuid();
|
|
var events = new List<AuditEvent>
|
|
{
|
|
SiteRunRow(20),
|
|
WriteRow(30, "A"),
|
|
Outbound(AuditChannel.Notification, AuditKind.NotifySend, AuditStatus.Submitted, 35, notificationId, "Planners", request: "{\"subject\":\"hi\"}"),
|
|
Outbound(AuditChannel.Notification, AuditKind.NotifyDeliver, AuditStatus.Attempted, 5000, notificationId, "Planners", node: "central-a", error: "smtp down", durationMs: 30),
|
|
Outbound(AuditChannel.Notification, AuditKind.NotifyDeliver, AuditStatus.Parked, 9000, notificationId, "Planners", node: "central-a", error: "retries exhausted"),
|
|
};
|
|
|
|
var graph = FlowGraphBuilder.Build(events);
|
|
|
|
var notify = Assert.Single(graph.Steps, s => s.Kind == FlowStepKind.Notify);
|
|
Assert.Equal(FlowStepOutcome.Parked, notify.Outcome);
|
|
Assert.Equal(3, notify.Lifecycle!.Count);
|
|
Assert.Equal("central-a", notify.Lifecycle[2].Node);
|
|
Assert.Equal("retries exhausted", notify.ErrorMessage);
|
|
Assert.Equal(8965, notify.DurationMs);
|
|
Assert.Equal("Parked", notify.Subtitle);
|
|
Assert.Equal("node-a", notify.Node);
|
|
var async = Assert.Single(graph.Edges, e => e.Async);
|
|
Assert.Equal(graph.Steps[1].EventId, async.FromEventId);
|
|
Assert.Equal(notify.EventId, async.ToEventId);
|
|
}
|
|
|
|
[Fact]
|
|
public void CachedSubmitOnly_IsPending_WithNoDuration()
|
|
{
|
|
var events = new List<AuditEvent>
|
|
{
|
|
SiteRunRow(20),
|
|
Outbound(AuditChannel.DbOutbound, AuditKind.CachedSubmit, AuditStatus.Submitted, 35, Guid.NewGuid(), "MesDb"),
|
|
};
|
|
|
|
var graph = FlowGraphBuilder.Build(events);
|
|
|
|
var db = Assert.Single(graph.Steps, s => s.Kind == FlowStepKind.DbCall);
|
|
Assert.Equal(FlowStepOutcome.Pending, db.Outcome);
|
|
Assert.Null(db.DurationMs);
|
|
Assert.Single(db.Lifecycle!);
|
|
var async = Assert.Single(graph.Edges);
|
|
Assert.True(async.Async);
|
|
Assert.Equal(graph.Steps[0].EventId, async.FromEventId);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseLinksFromTheInboundExecutionsLastStep_NotTheLastCascadedStep()
|
|
{
|
|
var events = DelmiaShaped();
|
|
var graph = FlowGraphBuilder.Build(events);
|
|
var response = Assert.Single(graph.Steps, s => s.Kind == FlowStepKind.Response);
|
|
var into = Assert.Single(graph.Edges, e => e.ToEventId == response.EventId);
|
|
Assert.Equal(graph.Steps[1].EventId, into.FromEventId);
|
|
Assert.Equal(FlowStepKind.RouteCall, graph.Steps[1].Kind);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResponseFallsBackToTheInboundRow_WhenNothingElseSharesItsExecution()
|
|
{
|
|
var inbound = ScadaBridgeAuditEventFactory.Create(AuditChannel.ApiInbound, AuditKind.InboundRequest, AuditStatus.Delivered,
|
|
occurredAtUtc: T0, actor: "k", target: "Ping", sourceNode: "central-a", executionId: Root, durationMs: 5, responseSummary: "{}");
|
|
var graph = FlowGraphBuilder.Build([inbound]);
|
|
Assert.Equal(2, graph.Steps.Count);
|
|
var edge = Assert.Single(graph.Edges);
|
|
Assert.Equal(graph.Steps[0].EventId, edge.FromEventId);
|
|
Assert.Equal(graph.Steps[1].EventId, edge.ToEventId);
|
|
}
|
|
}
|