From 9ca8ef5891c23ffb12b698ba942dfc8586ecdf9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 18:59:35 +0000 Subject: [PATCH 1/7] 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> --- .../RouteBuilderTests.cs | 526 ++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs new file mode 100644 index 0000000000..66235bb95d --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -0,0 +1,526 @@ +// 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 +{ + 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 RegisteredPorts { get; } = []; + + public List 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(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task AddHandler_VoidOverloads_RouteExpectedMessageAsync(int 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(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task AddHandler_ResultOverloads_RouteExpectedMessageAsync(int 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(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task AddCatchAll_VoidOverloads_RouteUnexpectedMessageAsync(int 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(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public async Task AddCatchAll_ResultOverloads_RouteUnexpectedMessageAsync(int 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 ValueTask.CompletedTask; + }); + routeBuilder.AddHandlerUntyped(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((message, context) => { }); + + // Assert + act.Should().Throw() + .WithMessage("*Use AddCatchAll()*"); + } + + [Fact] + public void AddHandler_DuplicateRegistrationWithoutOverwrite_ThrowsArgumentException() + { + // Arrange + RouteBuilder routeBuilder = new(null); + routeBuilder.AddHandler((message, context) => { }); + + // Act + Action act = () => routeBuilder.AddHandler((message, context) => { }); + + // Assert + act.Should().Throw() + .WithMessage("*already registered*"); + } + + [Fact] + public void AddHandler_OverwriteWithoutExistingRegistration_ThrowsArgumentException() + { + // Arrange + RouteBuilder routeBuilder = new(null); + + // Act + Action act = () => routeBuilder.AddHandler((message, context) => { }, overwrite: true); + + // Assert + act.Should().Throw() + .WithMessage("*has not yet been registered*"); + } + + [Fact] + public async Task AddHandler_OverwriteExistingRegistration_RoutesUpdatedHandlerAsync() + { + // Arrange + RouteBuilder routeBuilder = new(null); + routeBuilder.AddHandler((message, context) => context.SendMessageAsync("first")); + routeBuilder.AddHandler((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() + .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("port", (response, context, cancellationToken) => ValueTask.CompletedTask, out _); + + // Assert + act.Should().Throw() + .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("port", (response, context, cancellationToken) => + { + invocation.Capture(response, context, cancellationToken); + return ValueTask.CompletedTask; + }, 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("port", (response, context, cancellationToken) => ValueTask.CompletedTask, out _); + MessageRouter router = routeBuilder.Build(); + ExternalRequest request = ExternalRequest.Create(RequestPort.Create("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(); + result.Exception!.Message.Should().Contain("Unknown port"); + } + + private static void RegisterVoidHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, int overload) + { + switch (overload) + { + case 0: + routeBuilder.AddHandler((message, context, cancellationToken) => invocation.Capture(message, context, cancellationToken)); + break; + case 1: + routeBuilder.AddHandler((message, context) => invocation.Capture(message, context)); + break; + case 2: + routeBuilder.AddHandler((message, context, cancellationToken) => + { + invocation.Capture(message, context, cancellationToken); + return ValueTask.CompletedTask; + }); + break; + case 3: + routeBuilder.AddHandler((message, context) => + { + invocation.Capture(message, context); + return ValueTask.CompletedTask; + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(overload)); + } + } + + private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, int overload) + { + switch (overload) + { + case 0: + routeBuilder.AddHandler((message, context, cancellationToken) => + { + invocation.Capture(message, context, cancellationToken); + return message.ToUpperInvariant(); + }); + break; + case 1: + routeBuilder.AddHandler((message, context) => + { + invocation.Capture(message, context); + return message.ToUpperInvariant(); + }); + break; + case 2: + Func> asyncHandlerWithCancellation = (message, context, cancellationToken) => + { + invocation.Capture(message, context, cancellationToken); + return ValueTask.FromResult(message.ToUpperInvariant()); + }; + routeBuilder.AddHandler(asyncHandlerWithCancellation); + break; + case 3: + Func> asyncHandler = (message, context) => + { + invocation.Capture(message, context); + return ValueTask.FromResult(message.ToUpperInvariant()); + }; + routeBuilder.AddHandler(asyncHandler); + break; + default: + throw new ArgumentOutOfRangeException(nameof(overload)); + } + } + + private static void RegisterVoidCatchAll(RouteBuilder routeBuilder, HandlerInvocation invocation, int overload) + { + switch (overload) + { + case 0: + routeBuilder.AddCatchAll((message, context, cancellationToken) => invocation.Capture(message, context, cancellationToken)); + break; + case 1: + routeBuilder.AddCatchAll((message, context) => invocation.Capture(message, context)); + break; + case 2: + routeBuilder.AddCatchAll((message, context, cancellationToken) => + { + invocation.Capture(message, context, cancellationToken); + return ValueTask.CompletedTask; + }); + break; + case 3: + routeBuilder.AddCatchAll((message, context) => + { + invocation.Capture(message, context); + return ValueTask.CompletedTask; + }); + break; + default: + throw new ArgumentOutOfRangeException(nameof(overload)); + } + } + + private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInvocation invocation, int overload) + { + switch (overload) + { + case 0: + routeBuilder.AddCatchAll((message, context, cancellationToken) => + { + invocation.Capture(message, context, cancellationToken); + return message.As()!.Value.ToUpperInvariant(); + }); + break; + case 1: + routeBuilder.AddCatchAll((message, context) => + { + invocation.Capture(message, context); + return message.As()!.Value.ToUpperInvariant(); + }); + break; + case 2: + Func> asyncCatchAllWithCancellation = (message, context, cancellationToken) => + { + invocation.Capture(message, context, cancellationToken); + return ValueTask.FromResult(message.As()!.Value.ToUpperInvariant()); + }; + routeBuilder.AddCatchAll(asyncCatchAllWithCancellation); + break; + case 3: + Func> asyncCatchAll = (message, context) => + { + invocation.Capture(message, context); + return ValueTask.FromResult(message.As()!.Value.ToUpperInvariant()); + }; + routeBuilder.AddCatchAll(asyncCatchAll); + break; + default: + throw new ArgumentOutOfRangeException(nameof(overload)); + } + } + + private static bool UsesCancellationToken(int overload) => overload is 0 or 2; +} From 224d9ce6eecc99af1a89b2884dbac82e944e1030 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 19:01:06 +0000 Subject: [PATCH 2/7] 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> --- .../RouteBuilderTests.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs index 66235bb95d..a88fd4426b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -491,21 +491,21 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv routeBuilder.AddCatchAll((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return message.As()!.Value.ToUpperInvariant(); + return GetPayloadValue(message).ToUpperInvariant(); }); break; case 1: routeBuilder.AddCatchAll((message, context) => { invocation.Capture(message, context); - return message.As()!.Value.ToUpperInvariant(); + return GetPayloadValue(message).ToUpperInvariant(); }); break; case 2: Func> asyncCatchAllWithCancellation = (message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return ValueTask.FromResult(message.As()!.Value.ToUpperInvariant()); + return ValueTask.FromResult(GetPayloadValue(message).ToUpperInvariant()); }; routeBuilder.AddCatchAll(asyncCatchAllWithCancellation); break; @@ -513,7 +513,7 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv Func> asyncCatchAll = (message, context) => { invocation.Capture(message, context); - return ValueTask.FromResult(message.As()!.Value.ToUpperInvariant()); + return ValueTask.FromResult(GetPayloadValue(message).ToUpperInvariant()); }; routeBuilder.AddCatchAll(asyncCatchAll); break; @@ -523,4 +523,11 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv } private static bool UsesCancellationToken(int overload) => overload is 0 or 2; + + private static string GetPayloadValue(PortableValue message) + { + TestPayload? payload = message.As(); + payload.Should().NotBeNull(); + return payload.Value; + } } From 2b66bad6b35205d84070cd6b7a14f036dbcf0e8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 19:02:21 +0000 Subject: [PATCH 3/7] 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> --- .../RouteBuilderTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs index a88fd4426b..1e404c8a76 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -526,8 +526,8 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv private static string GetPayloadValue(PortableValue message) { - TestPayload? payload = message.As(); - payload.Should().NotBeNull(); - return payload.Value; + return message.As() is TestPayload payload + ? payload.Value + : throw new InvalidOperationException("Expected catch-all message payload to deserialize as TestPayload."); } } From e59b9327dbd640c5d1c0e638053385c3f7d3c4e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 19:03:44 +0000 Subject: [PATCH 4/7] 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> --- .../RouteBuilderTests.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs index 1e404c8a76..824efb2adb 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -423,21 +423,21 @@ private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvo routeBuilder.AddHandler((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return message.ToUpperInvariant(); + return NormalizeHandlerResult(message); }); break; case 1: routeBuilder.AddHandler((message, context) => { invocation.Capture(message, context); - return message.ToUpperInvariant(); + return NormalizeHandlerResult(message); }); break; case 2: Func> asyncHandlerWithCancellation = (message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return ValueTask.FromResult(message.ToUpperInvariant()); + return ValueTask.FromResult(NormalizeHandlerResult(message)); }; routeBuilder.AddHandler(asyncHandlerWithCancellation); break; @@ -445,7 +445,7 @@ private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvo Func> asyncHandler = (message, context) => { invocation.Capture(message, context); - return ValueTask.FromResult(message.ToUpperInvariant()); + return ValueTask.FromResult(NormalizeHandlerResult(message)); }; routeBuilder.AddHandler(asyncHandler); break; @@ -491,21 +491,21 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv routeBuilder.AddCatchAll((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return GetPayloadValue(message).ToUpperInvariant(); + return NormalizeCatchAllResult(message); }); break; case 1: routeBuilder.AddCatchAll((message, context) => { invocation.Capture(message, context); - return GetPayloadValue(message).ToUpperInvariant(); + return NormalizeCatchAllResult(message); }); break; case 2: Func> asyncCatchAllWithCancellation = (message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return ValueTask.FromResult(GetPayloadValue(message).ToUpperInvariant()); + return ValueTask.FromResult(NormalizeCatchAllResult(message)); }; routeBuilder.AddCatchAll(asyncCatchAllWithCancellation); break; @@ -513,7 +513,7 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv Func> asyncCatchAll = (message, context) => { invocation.Capture(message, context); - return ValueTask.FromResult(GetPayloadValue(message).ToUpperInvariant()); + return ValueTask.FromResult(NormalizeCatchAllResult(message)); }; routeBuilder.AddCatchAll(asyncCatchAll); break; @@ -524,6 +524,10 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv private static bool UsesCancellationToken(int overload) => overload is 0 or 2; + 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() is TestPayload payload From 1a6e4437893f63c91b4ed3f4d112162b0a7bf0a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 19:10:41 +0000 Subject: [PATCH 5/7] 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> --- .../RouteBuilderTests.cs | 91 ++++++++++--------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs index 824efb2adb..7230a31d19 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -12,6 +12,14 @@ 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 @@ -53,11 +61,11 @@ public ValueTask PostAsync(ExternalRequest request) } [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task AddHandler_VoidOverloads_RouteExpectedMessageAsync(int overload) + [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); @@ -86,11 +94,11 @@ public async Task AddHandler_VoidOverloads_RouteExpectedMessageAsync(int overloa } [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task AddHandler_ResultOverloads_RouteExpectedMessageAsync(int overload) + [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); @@ -120,11 +128,11 @@ public async Task AddHandler_ResultOverloads_RouteExpectedMessageAsync(int overl } [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task AddCatchAll_VoidOverloads_RouteUnexpectedMessageAsync(int overload) + [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); @@ -154,11 +162,11 @@ public async Task AddCatchAll_VoidOverloads_RouteUnexpectedMessageAsync(int over } [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public async Task AddCatchAll_ResultOverloads_RouteUnexpectedMessageAsync(int overload) + [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); @@ -386,24 +394,24 @@ public async Task AddPortHandler_UnknownPort_ReturnsExceptionResultAsync() result.Exception!.Message.Should().Contain("Unknown port"); } - private static void RegisterVoidHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, int overload) + private static void RegisterVoidHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload) { switch (overload) { - case 0: + case HandlerOverload.SyncWithCancellation: routeBuilder.AddHandler((message, context, cancellationToken) => invocation.Capture(message, context, cancellationToken)); break; - case 1: + case HandlerOverload.SyncWithoutCancellation: routeBuilder.AddHandler((message, context) => invocation.Capture(message, context)); break; - case 2: + case HandlerOverload.AsyncWithCancellation: routeBuilder.AddHandler((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); return ValueTask.CompletedTask; }); break; - case 3: + case HandlerOverload.AsyncWithoutCancellation: routeBuilder.AddHandler((message, context) => { invocation.Capture(message, context); @@ -415,25 +423,25 @@ private static void RegisterVoidHandler(RouteBuilder routeBuilder, HandlerInvoca } } - private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, int overload) + private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload) { switch (overload) { - case 0: + case HandlerOverload.SyncWithCancellation: routeBuilder.AddHandler((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); return NormalizeHandlerResult(message); }); break; - case 1: + case HandlerOverload.SyncWithoutCancellation: routeBuilder.AddHandler((message, context) => { invocation.Capture(message, context); return NormalizeHandlerResult(message); }); break; - case 2: + case HandlerOverload.AsyncWithCancellation: Func> asyncHandlerWithCancellation = (message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); @@ -441,7 +449,7 @@ private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvo }; routeBuilder.AddHandler(asyncHandlerWithCancellation); break; - case 3: + case HandlerOverload.AsyncWithoutCancellation: Func> asyncHandler = (message, context) => { invocation.Capture(message, context); @@ -454,24 +462,24 @@ private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvo } } - private static void RegisterVoidCatchAll(RouteBuilder routeBuilder, HandlerInvocation invocation, int overload) + private static void RegisterVoidCatchAll(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload) { switch (overload) { - case 0: + case HandlerOverload.SyncWithCancellation: routeBuilder.AddCatchAll((message, context, cancellationToken) => invocation.Capture(message, context, cancellationToken)); break; - case 1: + case HandlerOverload.SyncWithoutCancellation: routeBuilder.AddCatchAll((message, context) => invocation.Capture(message, context)); break; - case 2: + case HandlerOverload.AsyncWithCancellation: routeBuilder.AddCatchAll((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); return ValueTask.CompletedTask; }); break; - case 3: + case HandlerOverload.AsyncWithoutCancellation: routeBuilder.AddCatchAll((message, context) => { invocation.Capture(message, context); @@ -483,25 +491,25 @@ private static void RegisterVoidCatchAll(RouteBuilder routeBuilder, HandlerInvoc } } - private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInvocation invocation, int overload) + private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInvocation invocation, HandlerOverload overload) { switch (overload) { - case 0: + case HandlerOverload.SyncWithCancellation: routeBuilder.AddCatchAll((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); return NormalizeCatchAllResult(message); }); break; - case 1: + case HandlerOverload.SyncWithoutCancellation: routeBuilder.AddCatchAll((message, context) => { invocation.Capture(message, context); return NormalizeCatchAllResult(message); }); break; - case 2: + case HandlerOverload.AsyncWithCancellation: Func> asyncCatchAllWithCancellation = (message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); @@ -509,7 +517,7 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv }; routeBuilder.AddCatchAll(asyncCatchAllWithCancellation); break; - case 3: + case HandlerOverload.AsyncWithoutCancellation: Func> asyncCatchAll = (message, context) => { invocation.Capture(message, context); @@ -522,7 +530,8 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv } } - private static bool UsesCancellationToken(int overload) => overload is 0 or 2; + private static bool UsesCancellationToken(HandlerOverload overload) => + overload is HandlerOverload.SyncWithCancellation or HandlerOverload.AsyncWithCancellation; private static string NormalizeHandlerResult(string message) => message.ToUpperInvariant(); From 53787a354a23dd47360323ec1c6ce79951ff0c2e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 19:24:08 +0000 Subject: [PATCH 6/7] 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> --- .../RouteBuilderTests.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs index 7230a31d19..41a1d640ad 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -206,7 +206,7 @@ public async Task AddHandlerUntyped_VoidAndResultOverloads_RouteExpectedMessageA routeBuilder.AddHandlerUntyped(typeof(string), (message, context, token) => { voidInvocation.Capture(message, context, token); - return ValueTask.CompletedTask; + return default; }); routeBuilder.AddHandlerUntyped(typeof(int), (message, context, token) => { @@ -334,7 +334,7 @@ public void AddPortHandler_WithoutExternalRequestContext_ThrowsInvalidOperationE RouteBuilder routeBuilder = new(null); // Act - Action act = () => routeBuilder.AddPortHandler("port", (response, context, cancellationToken) => ValueTask.CompletedTask, out _); + Action act = () => routeBuilder.AddPortHandler("port", (response, context, cancellationToken) => default, out _); // Assert act.Should().Throw() @@ -351,7 +351,7 @@ public async Task AddPortHandler_RoutesMatchingExternalResponseAsync() routeBuilder.AddPortHandler("port", (response, context, cancellationToken) => { invocation.Capture(response, context, cancellationToken); - return ValueTask.CompletedTask; + return default; }, out PortBinding portBinding); await portBinding.PostRequestAsync("request", requestId: "req-1"); MessageRouter router = routeBuilder.Build(); @@ -380,7 +380,7 @@ public async Task AddPortHandler_UnknownPort_ReturnsExceptionResultAsync() // Arrange TestExternalRequestContext externalRequestContext = new(); RouteBuilder routeBuilder = new(externalRequestContext); - routeBuilder.AddPortHandler("port", (response, context, cancellationToken) => ValueTask.CompletedTask, out _); + routeBuilder.AddPortHandler("port", (response, context, cancellationToken) => default, out _); MessageRouter router = routeBuilder.Build(); ExternalRequest request = ExternalRequest.Create(RequestPort.Create("other"), "request", requestId: "req-1"); @@ -408,14 +408,14 @@ private static void RegisterVoidHandler(RouteBuilder routeBuilder, HandlerInvoca routeBuilder.AddHandler((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return ValueTask.CompletedTask; + return default; }); break; case HandlerOverload.AsyncWithoutCancellation: routeBuilder.AddHandler((message, context) => { invocation.Capture(message, context); - return ValueTask.CompletedTask; + return default; }); break; default: @@ -445,7 +445,7 @@ private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvo Func> asyncHandlerWithCancellation = (message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return ValueTask.FromResult(NormalizeHandlerResult(message)); + return new ValueTask(NormalizeHandlerResult(message)); }; routeBuilder.AddHandler(asyncHandlerWithCancellation); break; @@ -453,7 +453,7 @@ private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvo Func> asyncHandler = (message, context) => { invocation.Capture(message, context); - return ValueTask.FromResult(NormalizeHandlerResult(message)); + return new ValueTask(NormalizeHandlerResult(message)); }; routeBuilder.AddHandler(asyncHandler); break; @@ -476,14 +476,14 @@ private static void RegisterVoidCatchAll(RouteBuilder routeBuilder, HandlerInvoc routeBuilder.AddCatchAll((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return ValueTask.CompletedTask; + return default; }); break; case HandlerOverload.AsyncWithoutCancellation: routeBuilder.AddCatchAll((message, context) => { invocation.Capture(message, context); - return ValueTask.CompletedTask; + return default; }); break; default: @@ -513,7 +513,7 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv Func> asyncCatchAllWithCancellation = (message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); - return ValueTask.FromResult(NormalizeCatchAllResult(message)); + return new ValueTask(NormalizeCatchAllResult(message)); }; routeBuilder.AddCatchAll(asyncCatchAllWithCancellation); break; @@ -521,7 +521,7 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv Func> asyncCatchAll = (message, context) => { invocation.Capture(message, context); - return ValueTask.FromResult(NormalizeCatchAllResult(message)); + return new ValueTask(NormalizeCatchAllResult(message)); }; routeBuilder.AddCatchAll(asyncCatchAll); break; From beea1cf6b8f9f51fbdf225cc50a9bc15b6f464f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 19:49:13 +0000 Subject: [PATCH 7/7] 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> --- .../RouteBuilderTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs index 41a1d640ad..a734b82b66 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RouteBuilderTests.cs @@ -447,7 +447,7 @@ private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvo invocation.Capture(message, context, cancellationToken); return new ValueTask(NormalizeHandlerResult(message)); }; - routeBuilder.AddHandler(asyncHandlerWithCancellation); + routeBuilder.AddHandler(asyncHandlerWithCancellation); break; case HandlerOverload.AsyncWithoutCancellation: Func> asyncHandler = (message, context) => @@ -455,7 +455,7 @@ private static void RegisterResultHandler(RouteBuilder routeBuilder, HandlerInvo invocation.Capture(message, context); return new ValueTask(NormalizeHandlerResult(message)); }; - routeBuilder.AddHandler(asyncHandler); + routeBuilder.AddHandler(asyncHandler); break; default: throw new ArgumentOutOfRangeException(nameof(overload)); @@ -496,14 +496,14 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv switch (overload) { case HandlerOverload.SyncWithCancellation: - routeBuilder.AddCatchAll((message, context, cancellationToken) => + routeBuilder.AddCatchAll((message, context, cancellationToken) => { invocation.Capture(message, context, cancellationToken); return NormalizeCatchAllResult(message); }); break; case HandlerOverload.SyncWithoutCancellation: - routeBuilder.AddCatchAll((message, context) => + routeBuilder.AddCatchAll((message, context) => { invocation.Capture(message, context); return NormalizeCatchAllResult(message); @@ -515,7 +515,7 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv invocation.Capture(message, context, cancellationToken); return new ValueTask(NormalizeCatchAllResult(message)); }; - routeBuilder.AddCatchAll(asyncCatchAllWithCancellation); + routeBuilder.AddCatchAll(asyncCatchAllWithCancellation); break; case HandlerOverload.AsyncWithoutCancellation: Func> asyncCatchAll = (message, context) => @@ -523,7 +523,7 @@ private static void RegisterResultCatchAll(RouteBuilder routeBuilder, HandlerInv invocation.Capture(message, context); return new ValueTask(NormalizeCatchAllResult(message)); }; - routeBuilder.AddCatchAll(asyncCatchAll); + routeBuilder.AddCatchAll(asyncCatchAll); break; default: throw new ArgumentOutOfRangeException(nameof(overload));