File: HubFilterTests.cs
Web Access
Project: src\src\SignalR\server\SignalR\test\Microsoft.AspNetCore.SignalR.Tests\Microsoft.AspNetCore.SignalR.Tests.csproj (Microsoft.AspNetCore.SignalR.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
 
namespace Microsoft.AspNetCore.SignalR.Tests;
 
public class HubFilterTests : VerifiableLoggedTest
{
    [Fact]
    public async Task GlobalHubFilterByType_MethodsAreCalled()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter<VerifyMethodFilter>();
                });
 
                services.AddSingleton(tcsService);
            }, LoggerFactory);
 
            await AssertMethodsCalled(serviceProvider, tcsService);
        }
    }
 
    [Fact]
    public async Task GlobalHubFilterByInstance_MethodsAreCalled()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter(new VerifyMethodFilter(tcsService));
                });
            }, LoggerFactory);
 
            await AssertMethodsCalled(serviceProvider, tcsService);
        }
    }
 
    [Fact]
    public async Task PerHubFilterByInstance_MethodsAreCalled()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR().AddHubOptions<MethodHub>(options =>
                {
                    options.AddFilter(new VerifyMethodFilter(tcsService));
                });
            }, LoggerFactory);
 
            await AssertMethodsCalled(serviceProvider, tcsService);
        }
    }
 
    [Fact]
    public async Task PerHubFilterByCompileTimeType_MethodsAreCalled()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR().AddHubOptions<MethodHub>(options =>
                {
                    options.AddFilter<VerifyMethodFilter>();
                });
 
                services.AddSingleton(tcsService);
            }, LoggerFactory);
 
            await AssertMethodsCalled(serviceProvider, tcsService);
        }
    }
 
    [Fact]
    public async Task PerHubFilterByRuntimeType_MethodsAreCalled()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR().AddHubOptions<MethodHub>(options =>
                {
                    options.AddFilter(typeof(VerifyMethodFilter));
                });
 
                services.AddSingleton(tcsService);
            }, LoggerFactory);
 
            await AssertMethodsCalled(serviceProvider, tcsService);
        }
    }
 
    private async Task AssertMethodsCalled(IServiceProvider serviceProvider, TcsService tcsService)
    {
        var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
        using (var client = new TestClient())
        {
            var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
            await tcsService.StartedMethod.Task.DefaultTimeout();
            await client.Connected.DefaultTimeout();
            await tcsService.EndMethod.Task.DefaultTimeout();
 
            tcsService.Reset();
            var message = await client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!").DefaultTimeout();
            await tcsService.EndMethod.Task.DefaultTimeout();
            tcsService.Reset();
 
            Assert.Null(message.Error);
 
            client.Dispose();
 
            await connectionHandlerTask.DefaultTimeout();
 
            await tcsService.EndMethod.Task.DefaultTimeout();
        }
    }
 
    [Fact]
    public async Task HubFilterDoesNotNeedToImplementMethods()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR().AddHubOptions<DynamicTestHub>(options =>
                {
                    options.AddFilter(typeof(EmptyFilter));
                });
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<DynamicTestHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await client.Connected.DefaultTimeout();
 
                var completion = await client.InvokeAsync(nameof(DynamicTestHub.Echo), "hello");
                Assert.Null(completion.Error);
                Assert.Equal("hello", completion.Result);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task MutlipleFilters_MethodsAreCalled()
    {
        using (StartVerifiableLog())
        {
            var tcsService1 = new TcsService();
            var tcsService2 = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter(new VerifyMethodFilter(tcsService1));
                    options.AddFilter(new VerifyMethodFilter(tcsService2));
                });
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await tcsService1.StartedMethod.Task.DefaultTimeout();
                await tcsService2.StartedMethod.Task.DefaultTimeout();
                await client.Connected.DefaultTimeout();
                await tcsService1.EndMethod.Task.DefaultTimeout();
                await tcsService2.EndMethod.Task.DefaultTimeout();
 
                tcsService1.Reset();
                tcsService2.Reset();
                var message = await client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!").DefaultTimeout();
                await tcsService1.EndMethod.Task.DefaultTimeout();
                await tcsService2.EndMethod.Task.DefaultTimeout();
                tcsService1.Reset();
                tcsService2.Reset();
 
                Assert.Null(message.Error);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
 
                await tcsService1.EndMethod.Task.DefaultTimeout();
                await tcsService2.EndMethod.Task.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task MixingTypeAndInstanceGlobalFilters_MethodsAreCalled()
    {
        using (StartVerifiableLog())
        {
            var tcsService1 = new TcsService();
            var tcsService2 = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter(new VerifyMethodFilter(tcsService1));
                    options.AddFilter<VerifyMethodFilter>();
                });
 
                services.AddSingleton(tcsService2);
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await tcsService1.StartedMethod.Task.DefaultTimeout();
                await tcsService2.StartedMethod.Task.DefaultTimeout();
                await client.Connected.DefaultTimeout();
                await tcsService1.EndMethod.Task.DefaultTimeout();
                await tcsService2.EndMethod.Task.DefaultTimeout();
 
                tcsService1.Reset();
                tcsService2.Reset();
                var message = await client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!").DefaultTimeout();
                await tcsService1.EndMethod.Task.DefaultTimeout();
                await tcsService2.EndMethod.Task.DefaultTimeout();
                tcsService1.Reset();
                tcsService2.Reset();
 
                Assert.Null(message.Error);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
 
                await tcsService1.EndMethod.Task.DefaultTimeout();
                await tcsService2.EndMethod.Task.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task MixingTypeAndInstanceHubSpecificFilters_MethodsAreCalled()
    {
        using (StartVerifiableLog())
        {
            var tcsService1 = new TcsService();
            var tcsService2 = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR()
                .AddHubOptions<MethodHub>(options =>
                {
                    options.AddFilter(new VerifyMethodFilter(tcsService1));
                    options.AddFilter<VerifyMethodFilter>();
                });
 
                services.AddSingleton(tcsService2);
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await tcsService1.StartedMethod.Task.DefaultTimeout();
                await tcsService2.StartedMethod.Task.DefaultTimeout();
                await client.Connected.DefaultTimeout();
                await tcsService1.EndMethod.Task.DefaultTimeout();
                await tcsService2.EndMethod.Task.DefaultTimeout();
 
                tcsService1.Reset();
                tcsService2.Reset();
                var message = await client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!").DefaultTimeout();
                await tcsService1.EndMethod.Task.DefaultTimeout();
                await tcsService2.EndMethod.Task.DefaultTimeout();
                tcsService1.Reset();
                tcsService2.Reset();
 
                Assert.Null(message.Error);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
 
                await tcsService1.EndMethod.Task.DefaultTimeout();
                await tcsService2.EndMethod.Task.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task GlobalFiltersRunInOrder()
    {
        using (StartVerifiableLog())
        {
            var syncPoint1 = SyncPoint.Create(3, out var syncPoints1);
            var syncPoint2 = SyncPoint.Create(3, out var syncPoints2);
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter(new SyncPointFilter(syncPoints1));
                    options.AddFilter(new SyncPointFilter(syncPoints2));
                });
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await syncPoints1[0].WaitForSyncPoint().DefaultTimeout();
                // Second filter wont run yet because first filter is waiting on SyncPoint
                Assert.False(syncPoints2[0].WaitForSyncPoint().IsCompleted);
                syncPoints1[0].Continue();
 
                await syncPoints2[0].WaitForSyncPoint().DefaultTimeout();
                syncPoints2[0].Continue();
                await client.Connected.DefaultTimeout();
 
                var invokeTask = client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!");
 
                await syncPoints1[1].WaitForSyncPoint().DefaultTimeout();
                // Second filter wont run yet because first filter is waiting on SyncPoint
                Assert.False(syncPoints2[1].WaitForSyncPoint().IsCompleted);
                syncPoints1[1].Continue();
 
                await syncPoints2[1].WaitForSyncPoint().DefaultTimeout();
                syncPoints2[1].Continue();
                var message = await invokeTask.DefaultTimeout();
 
                Assert.Null(message.Error);
 
                client.Dispose();
 
                await syncPoints1[2].WaitForSyncPoint().DefaultTimeout();
                // Second filter wont run yet because first filter is waiting on SyncPoint
                Assert.False(syncPoints2[2].WaitForSyncPoint().IsCompleted);
                syncPoints1[2].Continue();
 
                await syncPoints2[2].WaitForSyncPoint().DefaultTimeout();
                syncPoints2[2].Continue();
 
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task HubSpecificFiltersRunInOrder()
    {
        using (StartVerifiableLog())
        {
            var syncPoint1 = SyncPoint.Create(3, out var syncPoints1);
            var syncPoint2 = SyncPoint.Create(3, out var syncPoints2);
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR()
                .AddHubOptions<MethodHub>(options =>
                {
                    options.AddFilter(new SyncPointFilter(syncPoints1));
                    options.AddFilter(new SyncPointFilter(syncPoints2));
                });
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await syncPoints1[0].WaitForSyncPoint().DefaultTimeout();
                // Second filter wont run yet because first filter is waiting on SyncPoint
                Assert.False(syncPoints2[0].WaitForSyncPoint().IsCompleted);
                syncPoints1[0].Continue();
 
                await syncPoints2[0].WaitForSyncPoint().DefaultTimeout();
                syncPoints2[0].Continue();
                await client.Connected.DefaultTimeout();
 
                var invokeTask = client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!");
 
                await syncPoints1[1].WaitForSyncPoint().DefaultTimeout();
                // Second filter wont run yet because first filter is waiting on SyncPoint
                Assert.False(syncPoints2[1].WaitForSyncPoint().IsCompleted);
                syncPoints1[1].Continue();
 
                await syncPoints2[1].WaitForSyncPoint().DefaultTimeout();
                syncPoints2[1].Continue();
                var message = await invokeTask.DefaultTimeout();
 
                Assert.Null(message.Error);
 
                client.Dispose();
 
                await syncPoints1[2].WaitForSyncPoint().DefaultTimeout();
                // Second filter wont run yet because first filter is waiting on SyncPoint
                Assert.False(syncPoints2[2].WaitForSyncPoint().IsCompleted);
                syncPoints1[2].Continue();
 
                await syncPoints2[2].WaitForSyncPoint().DefaultTimeout();
                syncPoints2[2].Continue();
 
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task GlobalFiltersRunBeforeHubSpecificFilters()
    {
        using (StartVerifiableLog())
        {
            var syncPoint1 = SyncPoint.Create(3, out var syncPoints1);
            var syncPoint2 = SyncPoint.Create(3, out var syncPoints2);
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter(new SyncPointFilter(syncPoints1));
                })
                .AddHubOptions<MethodHub>(options =>
                {
                    options.AddFilter(new SyncPointFilter(syncPoints2));
                });
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await syncPoints1[0].WaitForSyncPoint().DefaultTimeout();
                // Second filter wont run yet because first filter is waiting on SyncPoint
                Assert.False(syncPoints2[0].WaitForSyncPoint().IsCompleted);
                syncPoints1[0].Continue();
 
                await syncPoints2[0].WaitForSyncPoint().DefaultTimeout();
                syncPoints2[0].Continue();
                await client.Connected.DefaultTimeout();
 
                var invokeTask = client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!");
 
                await syncPoints1[1].WaitForSyncPoint().DefaultTimeout();
                // Second filter wont run yet because first filter is waiting on SyncPoint
                Assert.False(syncPoints2[1].WaitForSyncPoint().IsCompleted);
                syncPoints1[1].Continue();
 
                await syncPoints2[1].WaitForSyncPoint().DefaultTimeout();
                syncPoints2[1].Continue();
                var message = await invokeTask.DefaultTimeout();
 
                Assert.Null(message.Error);
 
                client.Dispose();
 
                await syncPoints1[2].WaitForSyncPoint().DefaultTimeout();
                // Second filter wont run yet because first filter is waiting on SyncPoint
                Assert.False(syncPoints2[2].WaitForSyncPoint().IsCompleted);
                syncPoints1[2].Continue();
 
                await syncPoints2[2].WaitForSyncPoint().DefaultTimeout();
                syncPoints2[2].Continue();
 
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task FilterCanBeResolvedFromDI()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter<VerifyMethodFilter>();
                });
 
                // If this instance wasn't resolved, then the tcsService.StartedMethod waits would never trigger and fail the test
                services.AddSingleton(new VerifyMethodFilter(tcsService));
            }, LoggerFactory);
 
            await AssertMethodsCalled(serviceProvider, tcsService);
        }
    }
 
    [Fact]
    public async Task FiltersHaveTransientScopeByDefault()
    {
        using (StartVerifiableLog())
        {
            var counter = new FilterCounter();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter<CounterFilter>();
                });
 
                services.AddSingleton(counter);
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await client.Connected.DefaultTimeout();
                // Filter is transient, so these counts are reset every time the filter is created
                Assert.Equal(1, counter.OnConnectedAsyncCount);
                Assert.Equal(0, counter.InvokeMethodAsyncCount);
                Assert.Equal(0, counter.OnDisconnectedAsyncCount);
 
                var message = await client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!").DefaultTimeout();
                // Filter is transient, so these counts are reset every time the filter is created
                Assert.Equal(0, counter.OnConnectedAsyncCount);
                Assert.Equal(1, counter.InvokeMethodAsyncCount);
                Assert.Equal(0, counter.OnDisconnectedAsyncCount);
 
                Assert.Null(message.Error);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
 
                // Filter is transient, so these counts are reset every time the filter is created
                Assert.Equal(0, counter.OnConnectedAsyncCount);
                Assert.Equal(0, counter.InvokeMethodAsyncCount);
                Assert.Equal(1, counter.OnDisconnectedAsyncCount);
            }
        }
    }
 
    [Fact]
    public async Task FiltersCanBeSingletonIfAddedToDI()
    {
        using (StartVerifiableLog())
        {
            var counter = new FilterCounter();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter<CounterFilter>();
                });
 
                services.AddSingleton<CounterFilter>();
                services.AddSingleton(counter);
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await client.Connected.DefaultTimeout();
                Assert.Equal(1, counter.OnConnectedAsyncCount);
                Assert.Equal(0, counter.InvokeMethodAsyncCount);
                Assert.Equal(0, counter.OnDisconnectedAsyncCount);
 
                var message = await client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!").DefaultTimeout();
                Assert.Equal(1, counter.OnConnectedAsyncCount);
                Assert.Equal(1, counter.InvokeMethodAsyncCount);
                Assert.Equal(0, counter.OnDisconnectedAsyncCount);
 
                Assert.Null(message.Error);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
 
                Assert.Equal(1, counter.OnConnectedAsyncCount);
                Assert.Equal(1, counter.InvokeMethodAsyncCount);
                Assert.Equal(1, counter.OnDisconnectedAsyncCount);
            }
        }
    }
 
    [Fact]
    public async Task ConnectionContinuesIfOnConnectedAsyncThrowsAndFilterDoesNot()
    {
        using (StartVerifiableLog())
        {
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.EnableDetailedErrors = true;
                    options.AddFilter<NoExceptionFilter>();
                });
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<OnConnectedThrowsHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                // Verify connection still connected, can't invoke a method if the connection is disconnected
                var message = await client.InvokeAsync("Method");
                Assert.Equal("Failed to invoke 'Method' due to an error on the server. HubException: Method does not exist.", message.Error);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task ConnectionContinuesIfOnConnectedAsyncNotCalledByFilter()
    {
        using (StartVerifiableLog())
        {
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.EnableDetailedErrors = true;
                    options.AddFilter(new SkipNextFilter(skipOnConnected: true));
                });
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                // Verify connection still connected, can't invoke a method if the connection is disconnected
                var message = await client.InvokeAsync("Method");
                Assert.Equal("Failed to invoke 'Method' due to an error on the server. HubException: Method does not exist.", message.Error);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task FilterCanSkipCallingHubMethod()
    {
        using (StartVerifiableLog())
        {
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.AddFilter(new SkipNextFilter(skipInvoke: true));
                });
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                await client.Connected.DefaultTimeout();
 
                var message = await client.InvokeAsync(nameof(MethodHub.Echo), "Hello world!").DefaultTimeout();
 
                Assert.Null(message.Error);
                Assert.Null(message.Result);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task FiltersWithIDisposableAreDisposed()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.EnableDetailedErrors = true;
                    options.AddFilter<DisposableFilter>();
                });
 
                services.AddSingleton(tcsService);
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                // OnConnectedAsync creates and destroys the filter
                await tcsService.StartedMethod.Task.DefaultTimeout();
                tcsService.Reset();
 
                var message = await client.InvokeAsync("Echo", "Hello");
                Assert.Equal("Hello", message.Result);
                await tcsService.StartedMethod.Task.DefaultTimeout();
                tcsService.Reset();
 
                client.Dispose();
 
                // OnDisconnectedAsync creates and destroys the filter
                await tcsService.StartedMethod.Task.DefaultTimeout();
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task InstanceFiltersWithIDisposableAreNotDisposed()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.EnableDetailedErrors = true;
                    options.AddFilter(new DisposableFilter(tcsService));
                });
 
                services.AddSingleton(tcsService);
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                var message = await client.InvokeAsync("Echo", "Hello");
                Assert.Equal("Hello", message.Result);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
 
                Assert.False(tcsService.StartedMethod.Task.IsCompleted);
            }
        }
    }
 
    [Fact]
    public async Task FiltersWithIAsyncDisposableAreDisposed()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.EnableDetailedErrors = true;
                    options.AddFilter<AsyncDisposableFilter>();
                });
 
                services.AddSingleton(tcsService);
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                // OnConnectedAsync creates and destroys the filter
                await tcsService.StartedMethod.Task.DefaultTimeout();
                tcsService.Reset();
 
                var message = await client.InvokeAsync("Echo", "Hello");
                Assert.Equal("Hello", message.Result);
                await tcsService.StartedMethod.Task.DefaultTimeout();
                tcsService.Reset();
 
                client.Dispose();
 
                // OnDisconnectedAsync creates and destroys the filter
                await tcsService.StartedMethod.Task.DefaultTimeout();
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
 
    [Fact]
    public async Task InstanceFiltersWithIAsyncDisposableAreNotDisposed()
    {
        using (StartVerifiableLog())
        {
            var tcsService = new TcsService();
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.EnableDetailedErrors = true;
                    options.AddFilter(new AsyncDisposableFilter(tcsService));
                });
 
                services.AddSingleton(tcsService);
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                var message = await client.InvokeAsync("Echo", "Hello");
                Assert.Equal("Hello", message.Result);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
 
                Assert.False(tcsService.StartedMethod.Task.IsCompleted);
            }
        }
    }
 
    [Fact]
    public async Task InvokeFailsWhenFilterCallsNonExistantMethod()
    {
        bool ExpectedErrors(WriteContext writeContext)
        {
            return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" &&
                   writeContext.EventId.Name == "FailedInvokingHubMethod";
        }
 
        using (StartVerifiableLog(expectedErrorsFilter: ExpectedErrors))
        {
            var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(services =>
            {
                services.AddSignalR(options =>
                {
                    options.EnableDetailedErrors = true;
                    options.AddFilter<ChangeMethodFilter>();
                });
            }, LoggerFactory);
 
            var connectionHandler = serviceProvider.GetService<HubConnectionHandler<MethodHub>>();
 
            using (var client = new TestClient())
            {
                var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
 
                var message = await client.InvokeAsync("Echo", "Hello");
                Assert.Equal("An unexpected error occurred invoking 'Echo' on the server. HubException: Unknown hub method 'BaseMethod'", message.Error);
 
                client.Dispose();
 
                await connectionHandlerTask.DefaultTimeout();
            }
        }
    }
}