Merge wt/step-logs-t10: Task 10

This commit is contained in:
Joseph Doherty
2026-09-09 12:32:35 -04:00
3 changed files with 72 additions and 3 deletions
@@ -152,6 +152,30 @@ public class FlowDebugRunnerTests
Assert.Same(siteRun, result.Events[2]);
}
[Fact]
public async Task RunMethod_ScriptThrows_InboundRowCarriesErrorDetail()
{
// Step-logs D3: InboundScriptExecutor turns an unhandled script exception into a
// SCRIPT_ERROR InboundScriptResult whose ErrorDetail is the exception's own ToString() —
// never surfaced to the HTTP caller, but the Flow Debugger's inbound node reads it off
// the written audit row.
var method = new ApiMethod("M", "x") { Id = 21, TimeoutSeconds = 5 };
_apiRepo.GetApiMethodByIdAsync(21, Arg.Any<CancellationToken>()).Returns(method);
_executor.RegisterHandler("M", _ => throw new InvalidOperationException("boom"));
var result = await Runner().RunMethodAsync(_services.BuildServiceProvider(),
new FlowDebugRunMethodCommand(21, null), Deployer, CancellationToken.None);
Assert.False(result.Completed);
Assert.Equal("SCRIPT_ERROR", result.ErrorCode);
var inbound = Assert.Single(_central, e => e.AsRow().Kind == AuditKind.InboundRequest).AsRow();
Assert.NotNull(inbound.ErrorDetail);
Assert.StartsWith("System.InvalidOperationException: boom", inbound.ErrorDetail);
// The returned copy carries the same detail (redacted, same as every other field).
var returnedInbound = result.Events[0].AsRow();
Assert.StartsWith("System.InvalidOperationException: boom", returnedInbound.ErrorDetail);
}
[Fact]
public async Task RunMethod_UnknownMethod_IsNotFound()
{
@@ -389,6 +413,12 @@ public class FlowDebugRunnerTests
executionId: Guid.NewGuid(), target: "work") with
{ DetailsJson = detailsJson };
/// <summary>A <c>ScriptLog</c> row — the low-priority lane <see cref="FlowStepProjector.IsLogLine"/> recognizes.</summary>
private static AuditEvent LogRow() =>
ScadaBridgeAuditEventFactory.Create(
AuditChannel.ScriptTrace, AuditKind.ScriptLog, AuditStatus.Delivered,
executionId: Guid.NewGuid(), target: "work");
[Fact]
public async Task RunMethod_SinkEvents_AreRedacted_WhenARedactorIsRegistered()
{
@@ -454,6 +484,37 @@ public class FlowDebugRunnerTests
Assert.True(result.Truncated);
Assert.Equal(501, result.Events.Count); // inbound row + the 500 kept
Assert.False(result.LogsTruncated); // these are full-priority step rows, not log lines
}
[Fact]
public async Task RunMethod_NoLogRows_LogsTruncatedIsFalse()
{
WireSinkPushingMethod(33, [TraceRow("{}")]);
var result = await Runner().RunMethodAsync(_services.BuildServiceProvider(),
new FlowDebugRunMethodCommand(33, null), Deployer, CancellationToken.None);
Assert.True(result.Completed, result.Error);
Assert.False(result.LogsTruncated);
Assert.False(result.Truncated);
}
[Fact]
public async Task RunMethod_OverLowPriorityReserve_ReportsLogsTruncated_ButNotTruncated()
{
// FlowTraceCollector reserves the last LowPriorityReserveEvents (50) of DefaultMaxEvents
// (500) for full-priority step rows — a low-priority (ScriptLog) Add stops at 450 and
// sets LogsTruncated, leaving Truncated (the step-row fidelity signal) untouched.
WireSinkPushingMethod(34, Enumerable.Range(0, 451).Select(_ => LogRow()).ToList());
var result = await Runner().RunMethodAsync(_services.BuildServiceProvider(),
new FlowDebugRunMethodCommand(34, null), Deployer, CancellationToken.None);
Assert.True(result.Completed, result.Error);
Assert.True(result.LogsTruncated);
Assert.False(result.Truncated);
Assert.Equal(451, result.Events.Count); // inbound row + the 450 log rows kept
}
// --- Flow Debugger script entry (design 2026-09-08) ---