feat(inbound-api): executor threads the trace flag; endpoint honours ApiMethod.TraceAllCalls
Flow Debugger (design 2026-09-05 §5.2): InboundScriptExecutor.ExecuteAsync gains a trailing trace parameter that builds the script's Route helper WithTrace(trace); the inbound endpoint passes method.TraceAllCalls so a flagged integration method is traced on every real call. Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
This commit is contained in:
@@ -275,7 +275,10 @@ public static class EndpointExtensions
|
|||||||
|
|
||||||
var scriptResult = await executor.ExecuteAsync(
|
var scriptResult = await executor.ExecuteAsync(
|
||||||
method, paramResult.Parameters, routeHelper, timeout,
|
method, paramResult.Parameters, routeHelper, timeout,
|
||||||
httpContext.RequestAborted, parentExecutionId);
|
httpContext.RequestAborted, parentExecutionId,
|
||||||
|
// Flow Debugger (design 2026-09-05 §5.2): a method flagged TraceAllCalls traces every
|
||||||
|
// production call so Replay of a real integration call is complete.
|
||||||
|
trace: method.TraceAllCalls);
|
||||||
|
|
||||||
if (!scriptResult.Success)
|
if (!scriptResult.Success)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -330,7 +330,10 @@ public class InboundScriptExecutor
|
|||||||
// Deliberate ordering: this optional parameter trails the CancellationToken
|
// Deliberate ordering: this optional parameter trails the CancellationToken
|
||||||
// because it was appended additively for non-breaking contract evolution.
|
// because it was appended additively for non-breaking contract evolution.
|
||||||
// Every call site passes it by named argument (parentExecutionId:).
|
// Every call site passes it by named argument (parentExecutionId:).
|
||||||
Guid? parentExecutionId = null)
|
Guid? parentExecutionId = null,
|
||||||
|
// Flow Debugger (design 2026-09-05 §5.2): trace the routed site runs this method makes.
|
||||||
|
// Additive; the endpoint passes ApiMethod.TraceAllCalls, the Flow Debugger passes true.
|
||||||
|
bool trace = false)
|
||||||
{
|
{
|
||||||
// Keep the timeout source and the request-abort source
|
// Keep the timeout source and the request-abort source
|
||||||
// separable. A single linked CTS makes a genuine client disconnect
|
// separable. A single linked CTS makes a genuine client disconnect
|
||||||
@@ -396,7 +399,8 @@ public class InboundScriptExecutor
|
|||||||
parameters,
|
parameters,
|
||||||
route.WithDeadline(cts.Token)
|
route.WithDeadline(cts.Token)
|
||||||
.WithRequestAborted(cancellationToken)
|
.WithRequestAborted(cancellationToken)
|
||||||
.WithParentExecutionId(parentExecutionId),
|
.WithParentExecutionId(parentExecutionId)
|
||||||
|
.WithTrace(trace),
|
||||||
dbHelper,
|
dbHelper,
|
||||||
cts.Token);
|
cts.Token);
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,35 @@ public class InboundScriptExecutorTests
|
|||||||
_route = new RouteHelper(locator, router);
|
_route = new RouteHelper(locator, router);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteAsync_TraceFlag_ReachesTheRoutedCallRequest()
|
||||||
|
{
|
||||||
|
// Flow Debugger (design 2026-09-05 §5.2): ExecuteAsync(trace: true) builds the script's
|
||||||
|
// Route helper WithTrace(true), so a Route.To(...).Call(...) inside the script sends a
|
||||||
|
// traced RouteToCallRequest; the default stays untraced.
|
||||||
|
var locator = Substitute.For<IInstanceLocator>();
|
||||||
|
var router = Substitute.For<IInstanceRouter>();
|
||||||
|
locator.GetSiteIdForInstanceAsync("inst", Arg.Any<CancellationToken>()).Returns("SiteA");
|
||||||
|
var captured = new List<RouteToCallRequest>();
|
||||||
|
router.RouteToCallAsync("SiteA", Arg.Do<RouteToCallRequest>(captured.Add), Arg.Any<CancellationToken>())
|
||||||
|
.Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, 1, null, DateTimeOffset.UtcNow));
|
||||||
|
var route = new RouteHelper(locator, router);
|
||||||
|
var method = new ApiMethod("routed", "x") { Id = 9, TimeoutSeconds = 10 };
|
||||||
|
_executor.RegisterHandler("routed", ctx => ctx.Route.To("inst").Call("work"));
|
||||||
|
|
||||||
|
var parent = Guid.NewGuid();
|
||||||
|
var traced = await _executor.ExecuteAsync(method, new Dictionary<string, object?>(), route,
|
||||||
|
TimeSpan.FromSeconds(10), parentExecutionId: parent, trace: true);
|
||||||
|
var plain = await _executor.ExecuteAsync(method, new Dictionary<string, object?>(), route, TimeSpan.FromSeconds(10));
|
||||||
|
|
||||||
|
Assert.True(traced.Success, traced.ErrorMessage);
|
||||||
|
Assert.True(plain.Success, plain.ErrorMessage);
|
||||||
|
Assert.Equal(2, captured.Count);
|
||||||
|
Assert.True(captured[0].Trace);
|
||||||
|
Assert.Equal(parent, captured[0].ParentExecutionId);
|
||||||
|
Assert.False(captured[1].Trace);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RegisteredHandler_ExecutesSuccessfully()
|
public async Task RegisteredHandler_ExecutesSuccessfully()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user