diff --git a/src/Tests/AliasVault.E2ETests/Common/AdminPlaywrightTest.cs b/src/Tests/AliasVault.E2ETests/Common/AdminPlaywrightTest.cs index 7941d0a87..0e60787a2 100644 --- a/src/Tests/AliasVault.E2ETests/Common/AdminPlaywrightTest.cs +++ b/src/Tests/AliasVault.E2ETests/Common/AdminPlaywrightTest.cs @@ -58,7 +58,7 @@ public class AdminPlaywrightTest : PlaywrightTest AppBaseUrl = "http://localhost:" + appPort + "/"; // Start Admin project in-memory. - _webAppFactory.HostUrl = "http://localhost:" + appPort; + _webAppFactory.Port = appPort; _webAppFactory.CreateDefaultClient(); await SetupPlaywrightBrowserAndContext(); diff --git a/src/Tests/AliasVault.E2ETests/Common/ClientPlaywrightTest.cs b/src/Tests/AliasVault.E2ETests/Common/ClientPlaywrightTest.cs index 073f1e1b3..8b1848f37 100644 --- a/src/Tests/AliasVault.E2ETests/Common/ClientPlaywrightTest.cs +++ b/src/Tests/AliasVault.E2ETests/Common/ClientPlaywrightTest.cs @@ -85,11 +85,11 @@ public class ClientPlaywrightTest : PlaywrightTest ApiBaseUrl = "http://localhost:" + apiPort + "/"; // Start WebAPI in-memory. - _apiFactory.HostUrl = "http://localhost:" + apiPort; + _apiFactory.Port = apiPort; _apiFactory.CreateDefaultClient(); // Start Blazor WASM in-memory. - _clientFactory.HostUrl = "http://localhost:" + appPort; + _clientFactory.Port = appPort; _clientFactory.CreateDefaultClient(); await SetupPlaywrightBrowserAndContext(); diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/KestrelTestServer.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/KestrelTestServer.cs new file mode 100644 index 000000000..3d5254342 --- /dev/null +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/KestrelTestServer.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.E2ETests.Infrastructure; + +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +/// +/// A that uses Kestrel as the server. +/// +public class KestrelTestServer : TestServer, IServer +{ + private readonly KestrelServer _server; + + /// + /// Initializes a new instance of the class. + /// + /// The to use. + public KestrelTestServer(IServiceProvider serviceProvider) + : base(serviceProvider) + { + // We get all the transport factories registered, and the first one is the correct one + // Getting the IConnectionListenerFactory directly from the service provider does not work + var transportFactory = serviceProvider.GetRequiredService>().First(); + + var kestrelOptions = serviceProvider.GetRequiredService>(); + var loggerFactory = serviceProvider.GetRequiredService(); + _server = new KestrelServer(kestrelOptions, transportFactory, loggerFactory); + } + + /// + async Task IServer.StartAsync(IHttpApplication application, CancellationToken cancellationToken) + { + // We need to also invoke the TestServer's StartAsync method to ensure that the test server is started + // Because the TestServer's StartAsync method is implemented explicitly, we need to use reflection to invoke it + await InvokeExplicitInterfaceMethod(nameof(IServer.StartAsync), typeof(TContext), [application, cancellationToken]); + + // We also start the Kestrel server in order for localhost to work + await _server.StartAsync(application, cancellationToken); + } + + /// + async Task IServer.StopAsync(CancellationToken cancellationToken) + { + await InvokeExplicitInterfaceMethod(nameof(IServer.StopAsync), null, [cancellationToken]); + await _server.StopAsync(cancellationToken); + } + + private Task InvokeExplicitInterfaceMethod(string methodName, Type? genericParameter, object[] args) + { + var baseMethod = typeof(TestServer).GetInterfaceMap(typeof(IServer)).TargetMethods.First(m => m.Name.EndsWith(methodName)); + var method = genericParameter == null ? baseMethod : baseMethod.MakeGenericMethod(genericParameter); + var task = method.Invoke(this, args) as Task ?? throw new InvalidOperationException("Task not returned"); + return task; + } +} diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs index e390940b1..27f723b29 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs @@ -11,6 +11,7 @@ using System.Data.Common; using AliasServerDb; using AliasVault.Admin.Services; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -49,9 +50,9 @@ public class WebApplicationAdminFactoryFixture : WebApplicationFact } /// - /// Gets or sets the URL the web application host will listen on. + /// Gets or sets the port the web application kestrel host will listen on. /// - public string HostUrl { get; set; } = "https://localhost:5003"; + public int Port { get; set; } = 5003; /// /// Returns the DbContext instance for the test. This can be used to seed the database with test data. @@ -82,28 +83,23 @@ public class WebApplicationAdminFactoryFixture : WebApplicationFact /// protected override IHost CreateHost(IHostBuilder builder) { - var dummyHost = builder.Build(); + builder.ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port)); + webHostBuilder.ConfigureServices(s => s.AddSingleton()); + }); - builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel()); - - var host = builder.Build(); - host.Start(); + var host = base.CreateHost(builder); // Get the DbContextFactory instance and store it for later use during tests. _dbContextFactory = host.Services.GetRequiredService>(); - // This delay prevents "ERR_CONNECTION_REFUSED" errors - // which happened like 1 out of 10 times when running tests. - Thread.Sleep(100); - - return dummyHost; + return host; } /// protected override void ConfigureWebHost(IWebHostBuilder builder) { - builder.UseUrls(HostUrl); - SetEnvironmentVariables(); builder.ConfigureServices(services => @@ -129,19 +125,13 @@ public class WebApplicationAdminFactoryFixture : WebApplicationFact /// The to modify. private static void RemoveExistingRegistrations(IServiceCollection services) { - var descriptorsToRemove = new[] - { - services.SingleOrDefault(d => d.ServiceType == typeof(IDbContextFactory)), - services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)), - services.SingleOrDefault(d => d.ServiceType == typeof(VersionedContentService)), - }; + var descriptorsToRemove = services.Where(d => + d.ServiceType.ToString().Contains("AliasServerDbContext") || + d.ServiceType == typeof(VersionedContentService)).ToList(); foreach (var descriptor in descriptorsToRemove) { - if (descriptor != null) - { - services.Remove(descriptor); - } + services.Remove(descriptor); } } diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs index 6a57df006..b7a5cfc22 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs @@ -11,6 +11,7 @@ using System.Data.Common; using AliasServerDb; using AliasVault.Shared.Providers.Time; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -49,9 +50,9 @@ public class WebApplicationApiFactoryFixture : WebApplicationFactor } /// - /// Gets or sets the URL the web application host will listen on. + /// Gets or sets the port the web application kestrel host will listen on. /// - public string HostUrl { get; set; } = "https://localhost:5001"; + public int Port { get; set; } = 5001; /// /// Gets the time provider instance for mutating the current time in tests. @@ -87,28 +88,23 @@ public class WebApplicationApiFactoryFixture : WebApplicationFactor /// protected override IHost CreateHost(IHostBuilder builder) { - var dummyHost = builder.Build(); + builder.ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port)); + webHostBuilder.ConfigureServices(s => s.AddSingleton()); + }); - builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel()); - - var host = builder.Build(); - host.Start(); + var host = base.CreateHost(builder); // Get the DbContextFactory instance and store it for later use during tests. _dbContextFactory = host.Services.GetRequiredService>(); - // This delay prevents "ERR_CONNECTION_REFUSED" errors - // which happened like 1 out of 10 times when running tests. - Thread.Sleep(100); - - return dummyHost; + return host; } /// protected override void ConfigureWebHost(IWebHostBuilder builder) { - builder.UseUrls(HostUrl); - SetEnvironmentVariables(); builder.ConfigureServices(services => @@ -133,19 +129,13 @@ public class WebApplicationApiFactoryFixture : WebApplicationFactor /// The to modify. private static void RemoveExistingRegistrations(IServiceCollection services) { - var descriptorsToRemove = new[] - { - services.SingleOrDefault(d => d.ServiceType == typeof(IDbContextFactory)), - services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)), - services.SingleOrDefault(d => d.ServiceType == typeof(ITimeProvider)), - }; + var descriptorsToRemove = services.Where(d => + d.ServiceType.ToString().Contains("AliasServerDbContext") || + d.ServiceType == typeof(ITimeProvider)).ToList(); foreach (var descriptor in descriptorsToRemove) { - if (descriptor != null) - { - services.Remove(descriptor); - } + services.Remove(descriptor); } } diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationClientFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationClientFactoryFixture.cs index 8653749b4..a28be75bf 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationClientFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationClientFactoryFixture.cs @@ -8,7 +8,9 @@ namespace AliasVault.E2ETests.Infrastructure; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; /// @@ -19,30 +21,19 @@ public class WebApplicationClientFactoryFixture : WebApplicationFac where TEntryPoint : class { /// - /// Gets or sets the URL the web application host will listen on. + /// Gets or sets the port the web application kestrel host will listen on. /// - public string HostUrl { get; set; } = "https://localhost:5002"; - - /// - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - builder.UseUrls(HostUrl); - } + public int Port { get; set; } = 5002; /// protected override IHost CreateHost(IHostBuilder builder) { - var dummyHost = builder.Build(); + builder.ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port)); + webHostBuilder.ConfigureServices(s => s.AddSingleton()); + }); - builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel()); - - var host = builder.Build(); - host.Start(); - - // This delay prevents "ERR_CONNECTION_REFUSED" errors - // which happened like 1 out of 10 times when running tests. - Thread.Sleep(100); - - return dummyHost; + return base.CreateHost(builder); } }