mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
eb40535436
* Add RouteBuilder unit tests Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/012f3b3b-acb9-4869-9084-b767cbe1885b Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> * Address RouteBuilder test review feedback Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/012f3b3b-acb9-4869-9084-b767cbe1885b Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> * Fix RouteBuilder test nullability warning Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/012f3b3b-acb9-4869-9084-b767cbe1885b Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> * Refine RouteBuilder test helpers Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/012f3b3b-acb9-4869-9084-b767cbe1885b Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> * Refactor overload int constants to HandlerOverload enum Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/19397f58-a88a-41cf-bd85-588f520e0d0f Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> * Fix ValueTask compatibility with .NET Framework 4.7.2 Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/a8437809-0898-43a6-a950-09eb3417f58a Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> * Fix IDE0001 format errors - simplify generic type names Agent-Logs-Url: https://github.com/microsoft/agent-framework/sessions/8573214e-ec42-4969-ba94-76bdc8ad3e59 Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com> Co-authored-by: Jacob Alber <jaalber@microsoft.com>
547 lines
22 KiB
C#
547 lines
22 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using FluentAssertions;
|
|
using Microsoft.Agents.AI.Workflows.Execution;
|
|
|
|
namespace Microsoft.Agents.AI.Workflows.UnitTests;
|
|
|
|
public sealed class RouteBuilderTests
|
|
{
|
|
public enum HandlerOverload
|
|
{
|
|
SyncWithCancellation = 0,
|
|
SyncWithoutCancellation = 1,
|
|
AsyncWithCancellation = 2,
|
|
AsyncWithoutCancellation = 3,
|
|
}
|
|
|
|
private sealed record TestPayload(string Value);
|
|
|
|
private sealed class HandlerInvocation
|
|
{
|
|
public object? Message { get; private set; }
|
|
|
|
public IWorkflowContext? Context { get; private set; }
|
|
|
|
public CancellationToken CancellationToken { get; private set; }
|
|
|
|
public int InvocationCount { get; private set; }
|
|
|
|
public void Capture(object? message, IWorkflowContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
this.Message = message;
|
|
this.Context = context;
|
|
this.CancellationToken = cancellationToken;
|
|
this.InvocationCount++;
|
|
}
|
|
}
|
|
|
|
private sealed class TestExternalRequestContext : IExternalRequestContext, IExternalRequestSink
|
|
{
|
|
public List<RequestPort> RegisteredPorts { get; } = [];
|
|
|
|
public List<ExternalRequest> PostedRequests { get; } = [];
|
|
|
|
public IExternalRequestSink RegisterPort(RequestPort port)
|
|
{
|
|
this.RegisteredPorts.Add(port);
|
|
return this;
|
|
}
|
|
|
|
public ValueTask PostAsync(ExternalRequest request)
|
|
{
|
|
this.PostedRequests.Add(request);
|
|
return default;
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(HandlerOverload.SyncWithCancellation)]
|
|
[InlineData(HandlerOverload.SyncWithoutCancellation)]
|
|
[InlineData(HandlerOverload.AsyncWithCancellation)]
|
|
[InlineData(HandlerOverload.AsyncWithoutCancellation)]
|
|
public async Task AddHandler_VoidOverloads_RouteExpectedMessageAsync(HandlerOverload overload)
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
HandlerInvocation invocation = new();
|
|
CancellationToken cancellationToken = new CancellationTokenSource().Token;
|
|
RegisterVoidHandler(routeBuilder, invocation, overload);
|
|
MessageRouter router = routeBuilder.Build();
|
|
TestWorkflowContext context = new("executor");
|
|
|
|
// Act
|
|
CallResult? result = await router.RouteMessageAsync("hello", context, cancellationToken: cancellationToken);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.IsSuccess.Should().BeTrue();
|
|
result.IsVoid.Should().BeTrue();
|
|
result.Result.Should().BeNull();
|
|
invocation.InvocationCount.Should().Be(1);
|
|
invocation.Message.Should().Be("hello");
|
|
invocation.Context.Should().BeSameAs(context);
|
|
|
|
if (UsesCancellationToken(overload))
|
|
{
|
|
invocation.CancellationToken.Should().Be(cancellationToken);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(HandlerOverload.SyncWithCancellation)]
|
|
[InlineData(HandlerOverload.SyncWithoutCancellation)]
|
|
[InlineData(HandlerOverload.AsyncWithCancellation)]
|
|
[InlineData(HandlerOverload.AsyncWithoutCancellation)]
|
|
public async Task AddHandler_ResultOverloads_RouteExpectedMessageAsync(HandlerOverload overload)
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
HandlerInvocation invocation = new();
|
|
CancellationToken cancellationToken = new CancellationTokenSource().Token;
|
|
RegisterResultHandler(routeBuilder, invocation, overload);
|
|
MessageRouter router = routeBuilder.Build();
|
|
TestWorkflowContext context = new("executor");
|
|
|
|
// Act
|
|
CallResult? result = await router.RouteMessageAsync("hello", context, cancellationToken: cancellationToken);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.IsSuccess.Should().BeTrue();
|
|
result.IsVoid.Should().BeFalse();
|
|
result.Result.Should().Be("HELLO");
|
|
router.DefaultOutputTypes.Should().Contain(typeof(string));
|
|
invocation.InvocationCount.Should().Be(1);
|
|
invocation.Message.Should().Be("hello");
|
|
invocation.Context.Should().BeSameAs(context);
|
|
|
|
if (UsesCancellationToken(overload))
|
|
{
|
|
invocation.CancellationToken.Should().Be(cancellationToken);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(HandlerOverload.SyncWithCancellation)]
|
|
[InlineData(HandlerOverload.SyncWithoutCancellation)]
|
|
[InlineData(HandlerOverload.AsyncWithCancellation)]
|
|
[InlineData(HandlerOverload.AsyncWithoutCancellation)]
|
|
public async Task AddCatchAll_VoidOverloads_RouteUnexpectedMessageAsync(HandlerOverload overload)
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
HandlerInvocation invocation = new();
|
|
CancellationToken cancellationToken = new CancellationTokenSource().Token;
|
|
TestPayload payload = new("hello");
|
|
RegisterVoidCatchAll(routeBuilder, invocation, overload);
|
|
MessageRouter router = routeBuilder.Build();
|
|
TestWorkflowContext context = new("executor");
|
|
|
|
// Act
|
|
CallResult? result = await router.RouteMessageAsync(payload, context, cancellationToken: cancellationToken);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.IsSuccess.Should().BeTrue();
|
|
result.IsVoid.Should().BeTrue();
|
|
result.Result.Should().BeNull();
|
|
invocation.InvocationCount.Should().Be(1);
|
|
invocation.Message.Should().BeEquivalentTo(new PortableValue(payload));
|
|
invocation.Context.Should().BeSameAs(context);
|
|
|
|
if (UsesCancellationToken(overload))
|
|
{
|
|
invocation.CancellationToken.Should().Be(cancellationToken);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(HandlerOverload.SyncWithCancellation)]
|
|
[InlineData(HandlerOverload.SyncWithoutCancellation)]
|
|
[InlineData(HandlerOverload.AsyncWithCancellation)]
|
|
[InlineData(HandlerOverload.AsyncWithoutCancellation)]
|
|
public async Task AddCatchAll_ResultOverloads_RouteUnexpectedMessageAsync(HandlerOverload overload)
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
HandlerInvocation invocation = new();
|
|
CancellationToken cancellationToken = new CancellationTokenSource().Token;
|
|
TestPayload payload = new("hello");
|
|
RegisterResultCatchAll(routeBuilder, invocation, overload);
|
|
MessageRouter router = routeBuilder.Build();
|
|
TestWorkflowContext context = new("executor");
|
|
|
|
// Act
|
|
CallResult? result = await router.RouteMessageAsync(payload, context, cancellationToken: cancellationToken);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.IsSuccess.Should().BeTrue();
|
|
result.IsVoid.Should().BeFalse();
|
|
result.Result.Should().Be("HELLO");
|
|
invocation.InvocationCount.Should().Be(1);
|
|
invocation.Message.Should().BeEquivalentTo(new PortableValue(payload));
|
|
invocation.Context.Should().BeSameAs(context);
|
|
|
|
if (UsesCancellationToken(overload))
|
|
{
|
|
invocation.CancellationToken.Should().Be(cancellationToken);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddHandlerUntyped_VoidAndResultOverloads_RouteExpectedMessageAsync()
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
HandlerInvocation voidInvocation = new();
|
|
HandlerInvocation resultInvocation = new();
|
|
CancellationToken cancellationToken = new CancellationTokenSource().Token;
|
|
routeBuilder.AddHandlerUntyped(typeof(string), (message, context, token) =>
|
|
{
|
|
voidInvocation.Capture(message, context, token);
|
|
return default;
|
|
});
|
|
routeBuilder.AddHandlerUntyped<int>(typeof(int), (message, context, token) =>
|
|
{
|
|
resultInvocation.Capture(message, context, token);
|
|
return new((int)message + 1);
|
|
});
|
|
MessageRouter router = routeBuilder.Build();
|
|
TestWorkflowContext context = new("executor");
|
|
|
|
// Act
|
|
CallResult? voidResult = await router.RouteMessageAsync("hello", context, cancellationToken: cancellationToken);
|
|
CallResult? typedResult = await router.RouteMessageAsync(41, context, cancellationToken: cancellationToken);
|
|
|
|
// Assert
|
|
voidResult.Should().NotBeNull();
|
|
voidResult!.IsVoid.Should().BeTrue();
|
|
voidInvocation.Message.Should().Be("hello");
|
|
voidInvocation.Context.Should().BeSameAs(context);
|
|
voidInvocation.CancellationToken.Should().Be(cancellationToken);
|
|
|
|
typedResult.Should().NotBeNull();
|
|
typedResult!.Result.Should().Be(42);
|
|
router.DefaultOutputTypes.Should().Contain(typeof(int));
|
|
resultInvocation.Message.Should().Be(41);
|
|
resultInvocation.Context.Should().BeSameAs(context);
|
|
resultInvocation.CancellationToken.Should().Be(cancellationToken);
|
|
}
|
|
|
|
[Fact]
|
|
public void AddHandler_ForPortableValue_ThrowsInvalidOperationException()
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
|
|
// Act
|
|
Action act = () => routeBuilder.AddHandler<PortableValue>((message, context) => { });
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidOperationException>()
|
|
.WithMessage("*Use AddCatchAll()*");
|
|
}
|
|
|
|
[Fact]
|
|
public void AddHandler_DuplicateRegistrationWithoutOverwrite_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
routeBuilder.AddHandler<string>((message, context) => { });
|
|
|
|
// Act
|
|
Action act = () => routeBuilder.AddHandler<string>((message, context) => { });
|
|
|
|
// Assert
|
|
act.Should().Throw<ArgumentException>()
|
|
.WithMessage("*already registered*");
|
|
}
|
|
|
|
[Fact]
|
|
public void AddHandler_OverwriteWithoutExistingRegistration_ThrowsArgumentException()
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
|
|
// Act
|
|
Action act = () => routeBuilder.AddHandler<string>((message, context) => { }, overwrite: true);
|
|
|
|
// Assert
|
|
act.Should().Throw<ArgumentException>()
|
|
.WithMessage("*has not yet been registered*");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddHandler_OverwriteExistingRegistration_RoutesUpdatedHandlerAsync()
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
routeBuilder.AddHandler<string>((message, context) => context.SendMessageAsync("first"));
|
|
routeBuilder.AddHandler<string>((message, context) => context.SendMessageAsync("second"), overwrite: true);
|
|
MessageRouter router = routeBuilder.Build();
|
|
TestWorkflowContext context = new("executor");
|
|
|
|
// Act
|
|
_ = await router.RouteMessageAsync("hello", context);
|
|
|
|
// Assert
|
|
context.SentMessages.Should().ContainSingle().Which.Should().Be("second");
|
|
}
|
|
|
|
[Fact]
|
|
public void AddCatchAll_DuplicateRegistrationWithoutOverwrite_ThrowsInvalidOperationException()
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
routeBuilder.AddCatchAll((message, context) => { });
|
|
|
|
// Act
|
|
Action act = () => routeBuilder.AddCatchAll((message, context) => { });
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidOperationException>()
|
|
.WithMessage("*already registered*");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddCatchAll_OverwriteExistingRegistration_RoutesUpdatedHandlerAsync()
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
routeBuilder.AddCatchAll((message, context) => context.SendMessageAsync("first"));
|
|
routeBuilder.AddCatchAll((message, context) => context.SendMessageAsync("second"), overwrite: true);
|
|
MessageRouter router = routeBuilder.Build();
|
|
TestWorkflowContext context = new("executor");
|
|
|
|
// Act
|
|
_ = await router.RouteMessageAsync(new TestPayload("hello"), context);
|
|
|
|
// Assert
|
|
context.SentMessages.Should().ContainSingle().Which.Should().Be("second");
|
|
}
|
|
|
|
[Fact]
|
|
public void AddPortHandler_WithoutExternalRequestContext_ThrowsInvalidOperationException()
|
|
{
|
|
// Arrange
|
|
RouteBuilder routeBuilder = new(null);
|
|
|
|
// Act
|
|
Action act = () => routeBuilder.AddPortHandler<string, int>("port", (response, context, cancellationToken) => default, out _);
|
|
|
|
// Assert
|
|
act.Should().Throw<InvalidOperationException>()
|
|
.WithMessage("*external request context is required*");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddPortHandler_RoutesMatchingExternalResponseAsync()
|
|
{
|
|
// Arrange
|
|
TestExternalRequestContext externalRequestContext = new();
|
|
RouteBuilder routeBuilder = new(externalRequestContext);
|
|
HandlerInvocation invocation = new();
|
|
routeBuilder.AddPortHandler<string, int>("port", (response, context, cancellationToken) =>
|
|
{
|
|
invocation.Capture(response, context, cancellationToken);
|
|
return default;
|
|
}, out PortBinding portBinding);
|
|
await portBinding.PostRequestAsync("request", requestId: "req-1");
|
|
MessageRouter router = routeBuilder.Build();
|
|
TestWorkflowContext context = new("executor");
|
|
CancellationToken cancellationToken = new CancellationTokenSource().Token;
|
|
ExternalResponse response = externalRequestContext.PostedRequests.Single().CreateResponse(42);
|
|
|
|
// Act
|
|
CallResult? result = await router.RouteMessageAsync(response, context, cancellationToken: cancellationToken);
|
|
|
|
// Assert
|
|
externalRequestContext.RegisteredPorts.Should().ContainSingle(port => port.Id == "port");
|
|
externalRequestContext.PostedRequests.Should().ContainSingle(request => request.RequestId == "req-1");
|
|
result.Should().NotBeNull();
|
|
result!.IsSuccess.Should().BeTrue();
|
|
result.Result.Should().BeSameAs(response);
|
|
invocation.InvocationCount.Should().Be(1);
|
|
invocation.Message.Should().Be(42);
|
|
invocation.Context.Should().BeSameAs(context);
|
|
invocation.CancellationToken.Should().Be(cancellationToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddPortHandler_UnknownPort_ReturnsExceptionResultAsync()
|
|
{
|
|
// Arrange
|
|
TestExternalRequestContext externalRequestContext = new();
|
|
RouteBuilder routeBuilder = new(externalRequestContext);
|
|
routeBuilder.AddPortHandler<string, int>("port", (response, context, cancellationToken) => default, out _);
|
|
MessageRouter router = routeBuilder.Build();
|
|
ExternalRequest request = ExternalRequest.Create(RequestPort.Create<string, int>("other"), "request", requestId: "req-1");
|
|
|
|
// Act
|
|
CallResult? result = await router.RouteMessageAsync(request.CreateResponse(42), new TestWorkflowContext("executor"));
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.IsSuccess.Should().BeFalse();
|
|
result.Exception.Should().BeOfType<InvalidOperationException>();
|
|
result.Exception!.Message.Should().Contain("Unknown port");
|
|
}
|
|
|
|
private static void RegisterVoidHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload)
|
|
{
|
|
switch (overload)
|
|
{
|
|
case HandlerOverload.SyncWithCancellation:
|
|
routeBuilder.AddHandler<string>((message, context, cancellationToken) => invocation.Capture(message, context, cancellationToken));
|
|
break;
|
|
case HandlerOverload.SyncWithoutCancellation:
|
|
routeBuilder.AddHandler<string>((message, context) => invocation.Capture(message, context));
|
|
break;
|
|
case HandlerOverload.AsyncWithCancellation:
|
|
routeBuilder.AddHandler<string>((message, context, cancellationToken) =>
|
|
{
|
|
invocation.Capture(message, context, cancellationToken);
|
|
return default;
|
|
});
|
|
break;
|
|
case HandlerOverload.AsyncWithoutCancellation:
|
|
routeBuilder.AddHandler<string>((message, context) =>
|
|
{
|
|
invocation.Capture(message, context);
|
|
return default;
|
|
});
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(overload));
|
|
}
|
|
}
|
|
|
|
private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload)
|
|
{
|
|
switch (overload)
|
|
{
|
|
case HandlerOverload.SyncWithCancellation:
|
|
routeBuilder.AddHandler<string, string>((message, context, cancellationToken) =>
|
|
{
|
|
invocation.Capture(message, context, cancellationToken);
|
|
return NormalizeHandlerResult(message);
|
|
});
|
|
break;
|
|
case HandlerOverload.SyncWithoutCancellation:
|
|
routeBuilder.AddHandler<string, string>((message, context) =>
|
|
{
|
|
invocation.Capture(message, context);
|
|
return NormalizeHandlerResult(message);
|
|
});
|
|
break;
|
|
case HandlerOverload.AsyncWithCancellation:
|
|
Func<string, IWorkflowContext, CancellationToken, ValueTask<string>> asyncHandlerWithCancellation = (message, context, cancellationToken) =>
|
|
{
|
|
invocation.Capture(message, context, cancellationToken);
|
|
return new ValueTask<string>(NormalizeHandlerResult(message));
|
|
};
|
|
routeBuilder.AddHandler(asyncHandlerWithCancellation);
|
|
break;
|
|
case HandlerOverload.AsyncWithoutCancellation:
|
|
Func<string, IWorkflowContext, ValueTask<string>> asyncHandler = (message, context) =>
|
|
{
|
|
invocation.Capture(message, context);
|
|
return new ValueTask<string>(NormalizeHandlerResult(message));
|
|
};
|
|
routeBuilder.AddHandler(asyncHandler);
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(overload));
|
|
}
|
|
}
|
|
|
|
private static void RegisterVoidCatchAll(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload)
|
|
{
|
|
switch (overload)
|
|
{
|
|
case HandlerOverload.SyncWithCancellation:
|
|
routeBuilder.AddCatchAll((message, context, cancellationToken) => invocation.Capture(message, context, cancellationToken));
|
|
break;
|
|
case HandlerOverload.SyncWithoutCancellation:
|
|
routeBuilder.AddCatchAll((message, context) => invocation.Capture(message, context));
|
|
break;
|
|
case HandlerOverload.AsyncWithCancellation:
|
|
routeBuilder.AddCatchAll((message, context, cancellationToken) =>
|
|
{
|
|
invocation.Capture(message, context, cancellationToken);
|
|
return default;
|
|
});
|
|
break;
|
|
case HandlerOverload.AsyncWithoutCancellation:
|
|
routeBuilder.AddCatchAll((message, context) =>
|
|
{
|
|
invocation.Capture(message, context);
|
|
return default;
|
|
});
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(overload));
|
|
}
|
|
}
|
|
|
|
private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload)
|
|
{
|
|
switch (overload)
|
|
{
|
|
case HandlerOverload.SyncWithCancellation:
|
|
routeBuilder.AddCatchAll((message, context, cancellationToken) =>
|
|
{
|
|
invocation.Capture(message, context, cancellationToken);
|
|
return NormalizeCatchAllResult(message);
|
|
});
|
|
break;
|
|
case HandlerOverload.SyncWithoutCancellation:
|
|
routeBuilder.AddCatchAll((message, context) =>
|
|
{
|
|
invocation.Capture(message, context);
|
|
return NormalizeCatchAllResult(message);
|
|
});
|
|
break;
|
|
case HandlerOverload.AsyncWithCancellation:
|
|
Func<PortableValue, IWorkflowContext, CancellationToken, ValueTask<string>> asyncCatchAllWithCancellation = (message, context, cancellationToken) =>
|
|
{
|
|
invocation.Capture(message, context, cancellationToken);
|
|
return new ValueTask<string>(NormalizeCatchAllResult(message));
|
|
};
|
|
routeBuilder.AddCatchAll(asyncCatchAllWithCancellation);
|
|
break;
|
|
case HandlerOverload.AsyncWithoutCancellation:
|
|
Func<PortableValue, IWorkflowContext, ValueTask<string>> asyncCatchAll = (message, context) =>
|
|
{
|
|
invocation.Capture(message, context);
|
|
return new ValueTask<string>(NormalizeCatchAllResult(message));
|
|
};
|
|
routeBuilder.AddCatchAll(asyncCatchAll);
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(overload));
|
|
}
|
|
}
|
|
|
|
private static bool UsesCancellationToken(HandlerOverload overload) =>
|
|
overload is HandlerOverload.SyncWithCancellation or HandlerOverload.AsyncWithCancellation;
|
|
|
|
private static string NormalizeHandlerResult(string message) => message.ToUpperInvariant();
|
|
|
|
private static string NormalizeCatchAllResult(PortableValue message) => GetPayloadValue(message).ToUpperInvariant();
|
|
|
|
private static string GetPayloadValue(PortableValue message)
|
|
{
|
|
return message.As<TestPayload>() is TestPayload payload
|
|
? payload.Value
|
|
: throw new InvalidOperationException("Expected catch-all message payload to deserialize as TestPayload.");
|
|
}
|
|
}
|