最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

asp.net core - SignalR Long Polling test fails on run passes on debug WebApi Integration Unit Test - Stack Overflow

programmeradmin0浏览0评论

The UseEndpoint MapHub is called when _configure?.Invoke(builder) is called.

when the test is run we see the following error:

System.InvalidOperationException : Endpoint /hub/notification/negotiate contains authorization metadata, but a middleware was not found that supports authorization. Configure your application startup by adding app.UseAuthorization() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.

this occurs when we await the connection StartAsync, however if we debug the test, everything works and the test passes.

this is the unit test:

public async Task CanConnectClientHubToServerUsingLongPolling()
{
    ConfigureServicesForTest((ctx, services) =>
    {
        services.AddNotification(builder =>
        {
            builder.Configure(options =>
            {
                options.Enabled = true;
            });

            builder.AddWebNotification(web =>
            {
                web.AddHubOptions(hub =>
                {
                });
            });
        });
    });

   Configure(app =>
   {
        app.UseNotificationServices();
   });

   INotifier notifier = null!;
   IAuthenticationTokensAccessor accessor = null!;

   FluentActions.Invoking(() =>
   {
       notifier = Services.GetRequiredService<INotifier>();
       accessor = Services.GetRequiredService<IAuthenticationTokensAccessor>();
   }).Should().NotThrow();

   var conn = CreateConnectionUsingLongPolling(
       Services.GetRequiredService<IOptionsMonitor<WebNotificationOptions>>().Get(Options.DefaultName),
       async () => (await accessor.GetAuthenticationTokensAsync("bob")).EncodedUserJwt);

   string expected = "this is a test";
   string recieved = "";

   conn.On<string>(HubMethods.RecieveSystemMessage, (msg) =>
   {
       Console.WriteLine($"System: {msg}");
   });

   conn.On<string>(HubMethods.RecieveMessage, (msg) =>
   {
       recieved = msg;

       _delayForMessageSentTokenSource.Cancel();
   });

   await conn.StartAsync();

   conn.ConnectionId.Should().NotBeNull();
   conn.State.Should().Be(HubConnectionState.Connected);

   await notifier.BroadCastAsync(expected);

   DelayAsync(TimeSpan.FromSeconds(60));

   recieved.Should().Be(expected);

   await conn.StopAsync();
}

for a couple days now I have been attempting to resolve this, however it is a head scratcher and I could use some help.

here is the program entry point used by WebApplicationFactory

using SubSonic.Configuration;
using SubSonic.Notification;

var builder = WebApplication.CreateBuilder(args);

if(!builder.Environment.IsTestIntegration())
{
    var section = builder.Configuration.GetRequiredSection("Settings");

    builder.Services.AddEnvironment(opt =>
    {
        opt.Environment = section.GetRequiredValue<string>("Environment");
    });

    builder.Services
        .AddHttpContextAccessor()
        .AddControllers();

    builder.Services.AddNotification(notify =>
    {
        notify.Configure(options =>
        { 
            options.Enabled = true;
        });

        notify.AddWebNotification(web =>
        {
        
        });
    });

    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
}

// program is primarily hosted in a test framework for integration testing of packages

var app = builder.Build();

if (!app.Environment.IsTestIntegration())
{

    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }

    app.UseNotificationServices();

    app.UseHttpsRedirection();

    app.UseAuthorization();

    app.MapControllers();
}

app.Run();

here is the log running without debug:

dbug: Microsoft.Extensions.Hosting.Internal.Host1 Hosting starting

dbug: SubSonic.Testing.WebApiTestFactory[0] calling UseRouting

dbug: SubSonic.Testing.WebApiTestFactory[0] Endpoint Routing Middleware Added False

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [application.Services, Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [server.Features, Microsoft.AspNetCore.Http.Features.FeatureCollection]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__EndpointRouteBuilder, Microsoft.AspNetCore.Routing.DefaultEndpointRouteBuilder]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__UseRouting, System.Func`2[Microsoft.AspNetCore.Builder.IApplicationBuilder,Microsoft.AspNetCore.Builder.IApplicationBuilder]]

dbug: SubSonic.Notification.NotificationProviderFactory[0] NotificationHub mapped to /hub/notification

dbug: Microsoft.Extensions.Hosting.Internal.Host2 Hosting started

here is the log running with debug:

dbug: Microsoft.Extensions.Hosting.Internal.Host1 Hosting starting

dbug: SubSonic.Testing.WebApiTestFactory[0] calling UseRouting

dbug: SubSonic.Testing.WebApiTestFactory[0] Endpoint Routing Middleware Added True

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [application.Services, Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [server.Features, Microsoft.AspNetCore.Http.Features.FeatureCollection]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__MiddlewareDescriptions, System.Collections.Generic.List`1[System.String]]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__EndpointRouteBuilder, Microsoft.AspNetCore.Routing.DefaultEndpointRouteBuilder]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__UseRouting, System.Func`2[Microsoft.AspNetCore.Builder.IApplicationBuilder,Microsoft.AspNetCore.Builder.IApplicationBuilder]]

dbug: SubSonic.Testing.WebApiTestFactory[0] Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware

dbug: SubSonic.Testing.WebApiTestFactory[0] Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware

dbug: SubSonic.Testing.WebApiTestFactory[0] Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware

dbug: SubSonic.Notification.NotificationProviderFactory[0] NotificationHub mapped to /hub/notification

dbug: Microsoft.Extensions.Hosting.Internal.Host2 Hosting started

The UseEndpoint MapHub is called when _configure?.Invoke(builder) is called.

when the test is run we see the following error:

System.InvalidOperationException : Endpoint /hub/notification/negotiate contains authorization metadata, but a middleware was not found that supports authorization. Configure your application startup by adding app.UseAuthorization() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.

this occurs when we await the connection StartAsync, however if we debug the test, everything works and the test passes.

this is the unit test:

public async Task CanConnectClientHubToServerUsingLongPolling()
{
    ConfigureServicesForTest((ctx, services) =>
    {
        services.AddNotification(builder =>
        {
            builder.Configure(options =>
            {
                options.Enabled = true;
            });

            builder.AddWebNotification(web =>
            {
                web.AddHubOptions(hub =>
                {
                });
            });
        });
    });

   Configure(app =>
   {
        app.UseNotificationServices();
   });

   INotifier notifier = null!;
   IAuthenticationTokensAccessor accessor = null!;

   FluentActions.Invoking(() =>
   {
       notifier = Services.GetRequiredService<INotifier>();
       accessor = Services.GetRequiredService<IAuthenticationTokensAccessor>();
   }).Should().NotThrow();

   var conn = CreateConnectionUsingLongPolling(
       Services.GetRequiredService<IOptionsMonitor<WebNotificationOptions>>().Get(Options.DefaultName),
       async () => (await accessor.GetAuthenticationTokensAsync("bob")).EncodedUserJwt);

   string expected = "this is a test";
   string recieved = "";

   conn.On<string>(HubMethods.RecieveSystemMessage, (msg) =>
   {
       Console.WriteLine($"System: {msg}");
   });

   conn.On<string>(HubMethods.RecieveMessage, (msg) =>
   {
       recieved = msg;

       _delayForMessageSentTokenSource.Cancel();
   });

   await conn.StartAsync();

   conn.ConnectionId.Should().NotBeNull();
   conn.State.Should().Be(HubConnectionState.Connected);

   await notifier.BroadCastAsync(expected);

   DelayAsync(TimeSpan.FromSeconds(60));

   recieved.Should().Be(expected);

   await conn.StopAsync();
}

for a couple days now I have been attempting to resolve this, however it is a head scratcher and I could use some help.

here is the program entry point used by WebApplicationFactory

using SubSonic.Configuration;
using SubSonic.Notification;

var builder = WebApplication.CreateBuilder(args);

if(!builder.Environment.IsTestIntegration())
{
    var section = builder.Configuration.GetRequiredSection("Settings");

    builder.Services.AddEnvironment(opt =>
    {
        opt.Environment = section.GetRequiredValue<string>("Environment");
    });

    builder.Services
        .AddHttpContextAccessor()
        .AddControllers();

    builder.Services.AddNotification(notify =>
    {
        notify.Configure(options =>
        { 
            options.Enabled = true;
        });

        notify.AddWebNotification(web =>
        {
        
        });
    });

    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
}

// program is primarily hosted in a test framework for integration testing of packages

var app = builder.Build();

if (!app.Environment.IsTestIntegration())
{

    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }

    app.UseNotificationServices();

    app.UseHttpsRedirection();

    app.UseAuthorization();

    app.MapControllers();
}

app.Run();

here is the log running without debug:

dbug: Microsoft.Extensions.Hosting.Internal.Host1 Hosting starting

dbug: SubSonic.Testing.WebApiTestFactory[0] calling UseRouting

dbug: SubSonic.Testing.WebApiTestFactory[0] Endpoint Routing Middleware Added False

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [application.Services, Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [server.Features, Microsoft.AspNetCore.Http.Features.FeatureCollection]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__EndpointRouteBuilder, Microsoft.AspNetCore.Routing.DefaultEndpointRouteBuilder]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__UseRouting, System.Func`2[Microsoft.AspNetCore.Builder.IApplicationBuilder,Microsoft.AspNetCore.Builder.IApplicationBuilder]]

dbug: SubSonic.Notification.NotificationProviderFactory[0] NotificationHub mapped to /hub/notification

dbug: Microsoft.Extensions.Hosting.Internal.Host2 Hosting started

here is the log running with debug:

dbug: Microsoft.Extensions.Hosting.Internal.Host1 Hosting starting

dbug: SubSonic.Testing.WebApiTestFactory[0] calling UseRouting

dbug: SubSonic.Testing.WebApiTestFactory[0] Endpoint Routing Middleware Added True

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [application.Services, Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [server.Features, Microsoft.AspNetCore.Http.Features.FeatureCollection]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__MiddlewareDescriptions, System.Collections.Generic.List`1[System.String]]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__EndpointRouteBuilder, Microsoft.AspNetCore.Routing.DefaultEndpointRouteBuilder]

dbug: SubSonic.Testing.WebApiTestFactory[0] Builder Property: [__UseRouting, System.Func`2[Microsoft.AspNetCore.Builder.IApplicationBuilder,Microsoft.AspNetCore.Builder.IApplicationBuilder]]

dbug: SubSonic.Testing.WebApiTestFactory[0] Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware

dbug: SubSonic.Testing.WebApiTestFactory[0] Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware

dbug: SubSonic.Testing.WebApiTestFactory[0] Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware

dbug: SubSonic.Notification.NotificationProviderFactory[0] NotificationHub mapped to /hub/notification

dbug: Microsoft.Extensions.Hosting.Internal.Host2 Hosting started

Share Improve this question edited 22 hours ago Jason Pan 21.9k2 gold badges19 silver badges42 bronze badges asked yesterday Kenneth CarterKenneth Carter 911 silver badge9 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

I ended up rewriting a couple files and separating the implementation of a WebApplication and a WebHost.

here are a few facts to keep track of:

  1. WebApplicationBuilder when the Build() method is called returns a WebApplication object which supports IApplicationBuilder and IEndpointRouteBuilder.
  2. WebHostBuilder when the Build() method is called returns an ApplicationBuilder which only supports IApplicationBuilder. This is also dependent on a Startup Approach to web host configuration of the services and configuration of middleware or any initialization that must occur after the Build.

I refactored the Program Entry Point

using Microsoft.AspNetCore;
using SubSonic.Configuration;
using SubSonic.Notification;
namespace Host
{
    public partial class Program
    {
        public static void Main(string[] args)
        {
            var webApp = CreateWebApplication(args);

            webApp.Run();
        }

        /// <summary>
        /// used to initialize a web application for deployment
        /// </summary>
        /// <param name="args"></param>
        /// <returns><see cref="WebApplication"/></returns>
        public static WebApplication CreateWebApplication(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            var section = builder.Configuration.GetRequiredSection("Settings");

            builder.Services.AddEnvironment(opt =>
            {
                opt.Environment = section.GetRequiredValue<string>("Environment");
            });
            builder.Services.AddSubSonicAuthentication(security =>
            {
               var section = builder.Configuration.GetRequiredSection("Security");

               security.SymmetricSecurityKey = Encoding.UTF8.GetBytes(section.GetRequiredValue<string>("SymmetricSecurityKey"));
               security.TokensExpireInSeconds = TimeSpan.FromSeconds(section.GetValue("TokensExpireInSeconds", 3600));
               security.RequireHttpsMetadata = false;
               security.Issuer = "localhost";
               security.Audience = "localhost";
            });

            builder.Services
                .AddHttpContextAccessor()
                .AddControllers();

            builder.Services.AddNotification(notify =>
            {
                notify.Configure(options =>
                {
                    options.Enabled = true;
                });

                notify.AddWebNotification();
            });

            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();
            app.UseAuthentication();
            app.UseAuthorization();

        app.MapNotificationHub(app.Services.GetRequiredService<IOptionsMonitor<WebNotificationOptions>>().Get(Options.DefaultName));
            app.MapControllers();

            return app;
        }

        /// <summary>
        /// this method is used to create IWebHostBuilder for WebApplicationFactory.CreateWebHostBuilder
        /// </summary>
        /// <param name="args"></param>
        /// <returns><see cref="IWebHostBuilder"></returns>
        public static IWebHostBuilder CreateWebHostBuilder(string[] args)
        {
            return WebHost.CreateDefaultBuilder(args);
        }
    }
}

we have one method that creates a WebApplication and another method that creates a IWebHostBuilder for the WebApplicationFactory

The following is implemented in class that inherits from WebApplicationFactory and set's up the startup approach.

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
   builder.UseConfiguration(_configuration);
   builder.ConfigureServices(_configureServices);
   builder.Configure((builder) =>
   {
       var logger = builder.ApplicationServices.GetRequiredService<ILogger<WebApplicationFactory<TEntryPoint>>>();

       _configure.Invoke(builder, logger);
   });
}

The following configure is implemented in a TestFixture which purpose is to rig packages for integration with a web host pipeline with full DI support.

private void Configure(IApplicationBuilder builder, ILogger logger)
{
   builder
      .UseHttpsRedirection();

   builder.UseRouting(); // this is the first call on this extension

   builder.UseAuthentication();
   builder.UseAuthorization();

   builder.UseEndpoints(erb =>
   {
       erb.MapControllers();
   });

   _configure?.Invoke(builder); //<- into the unit test in order to configure for test.
}

created an extension method called MapNotificationHub(this IEndpointRouteBuilder builder); to support the WebApplication.

we left the following code in the provider to be used by the IWebHostBuilder

public override void Configure(IApplicationBuilder builder, ILogger? logger = null)
{
   if (!(builder is IEndpointRouteBuilder))
   {
       builder.UseEndpoints(endpoints =>
       {
           endpoints.MapHub<NotificationHub>($"/hub/{Options.Endpoint}", Options.SetActionOptions<HttpConnectionDispatcherOptions>());
       });

       logger?.LogInformation("NotificationHub mapped to {Endpoint}", $"/hub/{Options.Endpoint}");
   }
}        

In your first image, we can see app.UseAuthentication(); in it.

But the program entry point used by WebApplicationFactory is missing this line, this is the keypoint.

According to the error message,we can change the code like below.

app.UseHttpsRedirection();

//Add this line
app.UseRouting();

//Add this line
app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论