From d4c588b2f526a775e1c1d8a20e5f5f473d0603e5 Mon Sep 17 00:00:00 2001 From: Eric StJohn Date: Thu, 2 Apr 2026 09:27:37 -0700 Subject: [PATCH] Fix flaky OutgoingFilter_MultipleFilters_ExecuteInOrder test (#1488) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MapMcpTests.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs index 5517789..678b270 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs @@ -5,6 +5,7 @@ using ModelContextProtocol.AspNetCore.Tests.Utils; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using ModelContextProtocol.Tests.Utils; using System.ComponentModel; using System.Diagnostics; using System.Net; @@ -394,6 +395,7 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI public async Task OutgoingFilter_MultipleFilters_ExecuteInOrder() { var executionOrder = new List(); + var allFiltersComplete = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Builder.Services.AddMcpServer() .WithHttpTransport(ConfigureStateless) @@ -411,6 +413,7 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI if (context.JsonRpcMessage is JsonRpcResponse r2 && r2.Result is JsonObject obj2 && obj2.ContainsKey("tools")) { executionOrder.Add("filter1-after"); + allFiltersComplete.TrySetResult(); } }); @@ -439,6 +442,14 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken); + // The outermost filter's "after" callback runs after the response has been + // sent to the client, so ListToolsAsync may return before it executes. + // Wait for it to complete before asserting, but use a timeout to avoid hanging + // the test indefinitely if the filter pipeline regresses. + using var allFiltersCts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken); + allFiltersCts.CancelAfter(TestConstants.DefaultTimeout); + await allFiltersComplete.Task.WaitAsync(allFiltersCts.Token); + Assert.Equal(["filter1-before", "filter2-before", "filter2-after", "filter1-after"], executionOrder); }