File: MapSignalRTests.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 Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;
 
namespace Microsoft.AspNetCore.SignalR.Tests;
 
public class MapSignalRTests
{
    [Fact]
    public void MapSignalRFailsForInvalidHub()
    {
        var ex = Assert.Throws<NotSupportedException>(() =>
        {
            using (var host = BuildWebHost(routes => routes.MapHub<InvalidHub>("/overloads")))
            {
                host.Start();
            }
        });
 
        Assert.Equal("Duplicate definitions of 'OverloadedMethod'. Overloading is not supported.", ex.Message);
    }
 
    [Fact]
    public void NotAddingSignalRServiceThrows()
    {
        var executedConfigure = false;
        var builder = new HostBuilder();
 
        builder.ConfigureWebHost(webHostBuilder =>
        {
            webHostBuilder
            .UseKestrel()
            .ConfigureServices(services =>
            {
                services.AddRouting();
            })
            .Configure(app =>
            {
                executedConfigure = true;
 
                var ex = Assert.Throws<InvalidOperationException>(() =>
                {
                    app.UseRouting();
                    app.UseEndpoints(endpoints =>
                    {
                        endpoints.MapHub<AuthHub>("/overloads");
                    });
                });
 
                Assert.Equal("Unable to find the required services. Please add all the required services by calling " +
                             "'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.", ex.Message);
            })
            .UseUrls("http://127.0.0.1:0");
        });
 
        using (var host = builder.Build())
        {
            host.Start();
        }
 
        Assert.True(executedConfigure);
    }
 
    [Fact]
    public void MapHubFindsAuthAttributeOnHub()
    {
        var authCount = 0;
        using (var host = BuildWebHost(routes => routes.MapHub<AuthHub>("/path", options =>
        {
            authCount += options.AuthorizationData.Count;
        })))
        {
            host.Start();
 
            var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
            // We register 2 endpoints (/negotiate and /)
            Assert.Collection(dataSource.Endpoints,
                endpoint =>
                {
                    Assert.Equal("/path/negotiate", endpoint.DisplayName);
                    Assert.Single(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>());
                },
                endpoint =>
                {
                    Assert.Equal("/path", endpoint.DisplayName);
                    Assert.Single(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>());
                });
        }
 
        Assert.Equal(0, authCount);
    }
 
    [Fact]
    public void MapHubFindsMetadataPolicyOnHub()
    {
        var authCount = 0;
        var policy1 = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build();
        var req = new TestRequirement();
        using (var host = BuildWebHost(routes => routes.MapHub<AuthHub>("/path", options =>
        {
            authCount += options.AuthorizationData.Count;
        })
        .RequireAuthorization(policy1)
        .RequireAuthorization(policy => policy.AddRequirements(req))))
        {
            host.Start();
 
            var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
            // We register 2 endpoints (/negotiate and /)
            Assert.Collection(dataSource.Endpoints,
                endpoint =>
                {
                    Assert.Equal("/path/negotiate", endpoint.DisplayName);
                    Assert.Single(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>());
                    var policies = endpoint.Metadata.GetOrderedMetadata<AuthorizationPolicy>();
                    Assert.Equal(2, policies.Count);
                    Assert.Equal(policy1, policies[0]);
                    Assert.Single(policies[1].Requirements);
                    Assert.Equal(req, policies[1].Requirements.First());
                },
                endpoint =>
                {
                    Assert.Equal("/path", endpoint.DisplayName);
                    Assert.Single(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>());
                    var policies = endpoint.Metadata.GetOrderedMetadata<AuthorizationPolicy>();
                    Assert.Equal(2, policies.Count);
                    Assert.Equal(policy1, policies[0]);
                    Assert.Single(policies[1].Requirements);
                    Assert.Equal(req, policies[1].Requirements.First());
                });
        }
 
        Assert.Equal(0, authCount);
    }
 
    [Fact]
    public void MapHubFindsAuthAttributeOnInheritedHub()
    {
        var authCount = 0;
        using (var host = BuildWebHost(routes => routes.MapHub<InheritedAuthHub>("/path", options =>
        {
            authCount += options.AuthorizationData.Count;
        })))
        {
            host.Start();
 
            var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
            // We register 2 endpoints (/negotiate and /)
            Assert.Collection(dataSource.Endpoints,
                endpoint =>
                {
                    Assert.Equal("/path/negotiate", endpoint.DisplayName);
                    Assert.Single(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>());
                },
                endpoint =>
                {
                    Assert.Equal("/path", endpoint.DisplayName);
                    Assert.Single(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>());
                });
        }
 
        Assert.Equal(0, authCount);
    }
 
    [Fact]
    public void MapHubFindsMultipleAuthAttributesOnDoubleAuthHub()
    {
        var authCount = 0;
        using (var host = BuildWebHost(routes => routes.MapHub<DoubleAuthHub>("/path", options =>
        {
            authCount += options.AuthorizationData.Count;
        })))
        {
            host.Start();
 
            var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
            // We register 2 endpoints (/negotiate and /)
            Assert.Collection(dataSource.Endpoints,
                endpoint =>
                {
                    Assert.Equal("/path/negotiate", endpoint.DisplayName);
                    Assert.Equal(2, endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>().Count);
                },
                endpoint =>
                {
                    Assert.Equal("/path", endpoint.DisplayName);
                    Assert.Equal(2, endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>().Count);
                });
        }
 
        Assert.Equal(0, authCount);
    }
 
    [Fact]
    public void MapHubEndPointRoutingFindsAttributesOnHub()
    {
        var authCount = 0;
        using (var host = BuildWebHost(routes => routes.MapHub<AuthHub>("/path", options =>
        {
            authCount += options.AuthorizationData.Count;
        })))
        {
            host.Start();
 
            var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
            // We register 2 endpoints (/negotiate and /)
            Assert.Collection(dataSource.Endpoints,
                endpoint =>
                {
                    Assert.Equal("/path/negotiate", endpoint.DisplayName);
                    Assert.Single(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>());
                },
                endpoint =>
                {
                    Assert.Equal("/path", endpoint.DisplayName);
                    Assert.Single(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>());
                });
        }
 
        Assert.Equal(0, authCount);
    }
 
    [Fact]
    public void MapHubEndPointRoutingFindsAttributesOnHubAndFromOptions()
    {
        var authCount = 0;
        HttpConnectionDispatcherOptions configuredOptions = null;
        using (var host = BuildWebHost(routes => routes.MapHub<AuthHub>("/path", options =>
        {
            authCount += options.AuthorizationData.Count;
            options.AuthorizationData.Add(new AuthorizeAttribute());
            configuredOptions = options;
        })))
        {
            host.Start();
 
            var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
            // We register 2 endpoints (/negotiate and /)
            Assert.Collection(dataSource.Endpoints,
                endpoint =>
                {
                    Assert.Equal("/path/negotiate", endpoint.DisplayName);
                    Assert.Equal(2, endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>().Count);
                },
                endpoint =>
                {
                    Assert.Equal("/path", endpoint.DisplayName);
                    Assert.Equal(2, endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>().Count);
                });
        }
 
        Assert.Equal(0, authCount);
    }
 
    [Fact]
    public void MapHubEndPointRoutingAppliesAttributesBeforeConventions()
    {
        void ConfigureRoutes(IEndpointRouteBuilder endpoints)
        {
            // This "Foo" policy should override the default auth attribute
            endpoints.MapHub<AuthHub>("/path")
                  .RequireAuthorization(new AuthorizeAttribute("Foo"));
        }
 
        using (var host = BuildWebHost(ConfigureRoutes))
        {
            host.Start();
 
            var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
            // We register 2 endpoints (/negotiate and /)
            Assert.Collection(dataSource.Endpoints,
                endpoint =>
                {
                    Assert.Equal("/path/negotiate", endpoint.DisplayName);
                    Assert.Collection(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>(),
                        auth => { },
                        auth =>
                        {
                            Assert.Equal("Foo", auth?.Policy);
                        });
                },
                endpoint =>
                {
                    Assert.Equal("/path", endpoint.DisplayName);
                    Assert.Collection(endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>(),
                        auth => { },
                        auth =>
                        {
                            Assert.Equal("Foo", auth?.Policy);
                        });
                });
        }
    }
 
    [Fact]
    public void MapHubEndPointRoutingAppliesHubMetadata()
    {
        void ConfigureRoutes(IEndpointRouteBuilder endpoints)
        {
            // This "Foo" policy should override the default auth attribute
            endpoints.MapHub<AuthHub>("/path");
        }
 
        using (var host = BuildWebHost(ConfigureRoutes))
        {
            host.Start();
 
            var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
            // We register 2 endpoints (/negotiate and /)
            Assert.Collection(dataSource.Endpoints,
                endpoint =>
                {
                    Assert.Equal("/path/negotiate", endpoint.DisplayName);
                    Assert.Equal(typeof(AuthHub), endpoint.Metadata.GetMetadata<HubMetadata>()?.HubType);
                    Assert.NotNull(endpoint.Metadata.GetMetadata<NegotiateMetadata>());
                },
                endpoint =>
                {
                    Assert.Equal("/path", endpoint.DisplayName);
                    Assert.Equal(typeof(AuthHub), endpoint.Metadata.GetMetadata<HubMetadata>()?.HubType);
                    Assert.Null(endpoint.Metadata.GetMetadata<NegotiateMetadata>());
                });
        }
    }
 
    [Fact]
    public void MapHubAppliesHubMetadata()
    {
        void ConfigureRoutes(IEndpointRouteBuilder routes)
        {
            // This "Foo" policy should override the default auth attribute
            routes.MapHub<AuthHub>("/path");
        }
 
        using (var host = BuildWebHost(ConfigureRoutes))
        {
            host.Start();
 
            var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
            // We register 2 endpoints (/negotiate and /)
            Assert.Collection(dataSource.Endpoints,
                endpoint =>
                {
                    Assert.Equal("/path/negotiate", endpoint.DisplayName);
                    Assert.Equal(typeof(AuthHub), endpoint.Metadata.GetMetadata<HubMetadata>()?.HubType);
                    Assert.NotNull(endpoint.Metadata.GetMetadata<NegotiateMetadata>());
                },
                endpoint =>
                {
                    Assert.Equal("/path", endpoint.DisplayName);
                    Assert.Equal(typeof(AuthHub), endpoint.Metadata.GetMetadata<HubMetadata>()?.HubType);
                    Assert.Null(endpoint.Metadata.GetMetadata<NegotiateMetadata>());
                });
        }
    }
 
    private class InvalidHub : Hub
    {
        public void OverloadedMethod(int num)
        {
        }
 
        public void OverloadedMethod(string message)
        {
        }
    }
 
    [Authorize]
    private class DoubleAuthHub : AuthHub
    {
    }
 
    private class InheritedAuthHub : AuthHub
    {
    }
 
    [Authorize]
    private class AuthHub : Hub
    {
    }
 
    private class TestRequirement : IAuthorizationRequirement
    {
    }
 
    private IHost BuildWebHost(Action<IEndpointRouteBuilder> configure)
    {
        return new HostBuilder()
            .ConfigureWebHost(webHostBuilder =>
            {
                webHostBuilder
                .UseKestrel()
                .ConfigureServices(services =>
                {
                    services.AddSignalR();
                })
                .Configure(app =>
                {
                    app.UseRouting();
                    app.UseEndpoints(endpoints => configure(endpoints));
                })
                .UseUrls("http://127.0.0.1:0");
            })
            .Build();
    }
}