From b348dcb4b23179331966cf6495682ef266f1e868 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Wed, 29 Jul 2026 13:34:43 -0700 Subject: [PATCH] Fix flaky RefreshChangingUserIdentifierClosesConnection test The Long Polling variant of this test is flaky. When the refresh changes the user identifier the server aborts the connection, and on dispose the client sends a best-effort DELETE carrying the refreshed token. That DELETE races the server's teardown: if the connection hasn't been removed from the manager yet the server rejects it with 403 and LongPollingTransport logs ErrorSendingDeleteRequest, failing VerifyNoErrorsScope. Once the connection is removed the DELETE gets a benign 404, so whether the error is logged depends purely on timing. Allow-list that specific teardown error so the timing race no longer fails the test. The connection is already closed either way, which the test still asserts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../HubConnectionTests.AuthenticationRefresh.cs | 15 ++++++++++++++- .../test/FunctionalTests/HubConnectionTests.cs | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.AuthenticationRefresh.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.AuthenticationRefresh.cs index 66c703498171..f374c13606e0 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.AuthenticationRefresh.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.AuthenticationRefresh.cs @@ -324,7 +324,20 @@ await Assert.ThrowsAsync( [MemberData(nameof(TransportTypes))] public async Task RefreshChangingUserIdentifierClosesConnection(HttpTransportType transportType) { - await using (var server = await StartServer()) + // Changing the user identifier on refresh aborts the connection on the server. With Long Polling the + // client sends a best-effort DELETE to clean up the connection when it's disposed. That DELETE carries + // the refreshed token (which now maps to a different user) and races the server's teardown: if the + // connection hasn't been removed from the manager yet, the server rejects the DELETE with 403 and the + // transport logs ErrorSendingDeleteRequest. Once the connection has been removed the DELETE gets a 404 + // and is treated as benign, so whether the error is logged depends purely on timing. The connection is + // already closed either way (the test asserts that below), so this teardown error is expected. + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == LongPollingTransportLoggerName && + writeContext.EventId.Name == "ErrorSendingDeleteRequest"; + } + + await using (var server = await StartServer(ExpectedErrors)) { // The SignalR UserIdentifier is derived from the JWT NameIdentifier claim, so changing // the user name on refresh changes the connection's UserIdentifier and should close it. diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index 533bea20e523..6408a1cfae98 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -30,6 +30,7 @@ public class HubConnectionTestsCollection : ICollectionFixture