This commit is contained in:
parent
5b48e0bfdb
commit
d92456e141
6 changed files with 95 additions and 36 deletions
|
|
@ -10,10 +10,10 @@ namespace Yavsc.Org.Tests
|
||||||
{
|
{
|
||||||
[Collection("Yavsc Server")]
|
[Collection("Yavsc Server")]
|
||||||
[Trait("regression", "oui")]
|
[Trait("regression", "oui")]
|
||||||
public class BaseTestContext : IClassFixture<WebServerFixture>, IDisposable
|
public abstract class BaseTestContext : IClassFixture<WebServerFixture>, IDisposable
|
||||||
{
|
{
|
||||||
public readonly WebServerFixture _serverFixture;
|
protected readonly WebServerFixture _serverFixture;
|
||||||
private readonly ITestOutputHelper _output;
|
protected readonly ITestOutputHelper _output;
|
||||||
|
|
||||||
public BaseTestContext(ITestOutputHelper output, WebServerFixture fixture)
|
public BaseTestContext(ITestOutputHelper output, WebServerFixture fixture)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,11 @@ namespace Yavsc.Org.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetOpenIdConfiguration_returns_ok()
|
public async Task GetOpenIdConfiguration_returns_ok()
|
||||||
{
|
{
|
||||||
using var client = _serverFixture.CreateHttpClient();
|
using var client = CreateHttpClient();
|
||||||
var response = await GetRaw(client, "/.well-known/openid-configuration");
|
var response = await GetRaw(client, "/.well-known/openid-configuration");
|
||||||
var payload = await response.Content.ReadAsStringAsync();
|
var payload = await response.Content.ReadAsStringAsync(
|
||||||
|
TestContext.Current.CancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
Assert.True(
|
Assert.True(
|
||||||
response.IsSuccessStatusCode,
|
response.IsSuccessStatusCode,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,8 @@
|
||||||
|
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
||||||
using Yavsc.Models;
|
|
||||||
|
|
||||||
namespace Yavsc.Org.Tests.Mandatory
|
namespace Yavsc.Org.Tests.Mandatory
|
||||||
{[Collection("Database")]
|
{[Collection("Database")]
|
||||||
[Trait("regression", "II")]
|
[Trait("regression", "II")]
|
||||||
[Trait("dev", "wip")]
|
[Trait("dev", "wip")]
|
||||||
public class Database: IClassFixture<WebServerFixture>, IDisposable
|
public class Database : IClassFixture<WebServerFixture>
|
||||||
{
|
{
|
||||||
readonly ITestOutputHelper output;
|
readonly ITestOutputHelper output;
|
||||||
readonly WebServerFixture _serverFixture;
|
readonly WebServerFixture _serverFixture;
|
||||||
|
|
@ -24,15 +18,6 @@ namespace Yavsc.Org.Tests.Mandatory
|
||||||
/// Install all our migrations in a fresh new database.
|
/// Install all our migrations in a fresh new database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_serverFixture!=null)
|
|
||||||
{
|
|
||||||
_serverFixture.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestDatabaseMigration()
|
public void TestDatabaseMigration()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,13 @@ namespace Yavsc.Org.Tests.Smoke;
|
||||||
/// <c>doc/architecture/decoupage-organisation.md</c>. The smoke
|
/// <c>doc/architecture/decoupage-organisation.md</c>. The smoke
|
||||||
/// here asserts the front-end side of the BC.
|
/// here asserts the front-end side of the BC.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BlogSmokeTests : SmokeTestBase, IClassFixture<TestWebApplicationFactory>
|
public class BlogSmokeTests : BaseTestContext, IClassFixture<TestWebApplicationFactory>
|
||||||
{
|
{
|
||||||
private readonly TestWebApplicationFactory _factory;
|
private readonly TestWebApplicationFactory _factory;
|
||||||
|
|
||||||
public BlogSmokeTests(TestWebApplicationFactory factory)
|
public BlogSmokeTests(TestWebApplicationFactory factory, ITestOutputHelper output,
|
||||||
|
WebServerFixture webServerFixture)
|
||||||
|
: base(output, webServerFixture)
|
||||||
{
|
{
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ public sealed class WebServerFixture : WebHostFixture
|
||||||
private static string? _sharedTestingUserName;
|
private static string? _sharedTestingUserName;
|
||||||
private static string? _sharedTestingUserPassword;
|
private static string? _sharedTestingUserPassword;
|
||||||
private static string? _sharedTestingUserEmail;
|
private static string? _sharedTestingUserEmail;
|
||||||
|
private static string? _sharedHttpsAuthority;
|
||||||
private static RecordingSmtpClientFactory? _sharedSmtpClientFactory;
|
private static RecordingSmtpClientFactory? _sharedSmtpClientFactory;
|
||||||
|
|
||||||
public IConfiguration? Configuration { get; private set; }
|
public IConfiguration? Configuration { get; private set; }
|
||||||
|
|
@ -78,10 +79,19 @@ public sealed class WebServerFixture : WebHostFixture
|
||||||
public RecordingSmtpClientFactory? SmtpClientFactory { get; private set; }
|
public RecordingSmtpClientFactory? SmtpClientFactory { get; private set; }
|
||||||
public ILogger? Logger { get; internal set; }
|
public ILogger? Logger { get; internal set; }
|
||||||
|
|
||||||
public string? HttpsAuthority => Addresses.FirstOrDefault(u => u.StartsWith("https:"));
|
public string? HttpsAuthority { get; private set; }
|
||||||
|
|
||||||
|
protected override WebApplicationOptions CreateBuilderOptions()
|
||||||
|
{
|
||||||
|
return new WebApplicationOptions
|
||||||
|
{
|
||||||
|
ApplicationName = typeof(Yavsc.Program).Assembly.GetName().Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected override WebApplication BuildApp(WebApplicationBuilder builder)
|
protected override WebApplication BuildApp(WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
var authority = $"https://localhost:{_httpsPort}";
|
HttpsAuthority = $"https://localhost:{HttpsPort}";
|
||||||
|
|
||||||
// WebApplication.CreateBuilder defaults WebRootPath to
|
// WebApplication.CreateBuilder defaults WebRootPath to
|
||||||
// {ContentRoot}/wwwroot. The test assembly runs from
|
// {ContentRoot}/wwwroot. The test assembly runs from
|
||||||
|
|
@ -100,7 +110,7 @@ public sealed class WebServerFixture : WebHostFixture
|
||||||
["Smtp:Port"] = "465",
|
["Smtp:Port"] = "465",
|
||||||
["Smtp:UserName"] = "test-user",
|
["Smtp:UserName"] = "test-user",
|
||||||
["Smtp:Password"] = "test-pass",
|
["Smtp:Password"] = "test-pass",
|
||||||
["Site:Authority"] = authority
|
["Site:Authority"] = HttpsAuthority
|
||||||
});
|
});
|
||||||
|
|
||||||
Configuration = builder.Configuration;
|
Configuration = builder.Configuration;
|
||||||
|
|
@ -183,6 +193,7 @@ public sealed class WebServerFixture : WebHostFixture
|
||||||
_sharedTestingUserName = TestingUserName;
|
_sharedTestingUserName = TestingUserName;
|
||||||
_sharedTestingUserPassword = TestingUserPassword;
|
_sharedTestingUserPassword = TestingUserPassword;
|
||||||
_sharedTestingUserEmail = TestingUserEmail;
|
_sharedTestingUserEmail = TestingUserEmail;
|
||||||
|
_sharedHttpsAuthority = HttpsAuthority;
|
||||||
_sharedLogger = app.Services.GetRequiredService<ILoggerFactory>().CreateLogger<WebServerFixture>();
|
_sharedLogger = app.Services.GetRequiredService<ILoggerFactory>().CreateLogger<WebServerFixture>();
|
||||||
Logger = _sharedLogger;
|
Logger = _sharedLogger;
|
||||||
SmtpClientFactory = smtpFactory;
|
SmtpClientFactory = smtpFactory;
|
||||||
|
|
@ -195,13 +206,42 @@ public sealed class WebServerFixture : WebHostFixture
|
||||||
using var scope = Services.CreateScope();
|
using var scope = Services.CreateScope();
|
||||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
db.Database.EnsureDeleted();
|
db.Database.EnsureDeleted();
|
||||||
|
db.Database.EnsureCreated();
|
||||||
if (db.Database.IsRelational())
|
if (db.Database.IsRelational())
|
||||||
{
|
{
|
||||||
db.Database.Migrate();
|
db.Database.Migrate();
|
||||||
|
ReseedAuthTestData(scope);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ReseedAuthTestData(scope);
|
||||||
|
}
|
||||||
|
|
||||||
db.Database.EnsureCreated();
|
private void ReseedAuthTestData(IServiceScope scope)
|
||||||
|
{
|
||||||
|
TestingUserName ??= "Tester";
|
||||||
|
TestingUserPassword ??= "Test123!";
|
||||||
|
TestingUserEmail ??= "test@no-reply.com";
|
||||||
|
TestClientId ??= "testClientId";
|
||||||
|
TestClientSecret ??= Guid.CreateVersion7().ToString();
|
||||||
|
|
||||||
|
TestingUser = null;
|
||||||
|
EnsureUser(TestingUserName, TestingUserPassword, TestingUserEmail, scope);
|
||||||
|
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
TestingUser = db.Users.FirstOrDefault(u => u.UserName == TestingUserName);
|
||||||
|
|
||||||
|
var configDb = scope.ServiceProvider.GetRequiredService<IdentityServer8.EntityFramework.DbContexts.ConfigurationDbContext>();
|
||||||
|
var hasClient = configDb.Set<Client>().Any(c => c.ClientId == TestClientId);
|
||||||
|
if (!hasClient)
|
||||||
|
{
|
||||||
|
AddAuthorizedClient(scope, TestClientId, TestClientSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sharedTestClientId = TestClientId;
|
||||||
|
_sharedTestClientSecret = TestClientSecret;
|
||||||
|
_sharedTestingUserName = TestingUserName;
|
||||||
|
_sharedTestingUserPassword = TestingUserPassword;
|
||||||
|
_sharedTestingUserEmail = TestingUserEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<WebApplication> ConfigurePipelineAsync(WebApplication app)
|
protected override async Task<WebApplication> ConfigurePipelineAsync(WebApplication app)
|
||||||
|
|
@ -226,6 +266,7 @@ public sealed class WebServerFixture : WebHostFixture
|
||||||
TestingUserName = _sharedTestingUserName;
|
TestingUserName = _sharedTestingUserName;
|
||||||
TestingUserPassword = _sharedTestingUserPassword;
|
TestingUserPassword = _sharedTestingUserPassword;
|
||||||
TestingUserEmail = _sharedTestingUserEmail;
|
TestingUserEmail = _sharedTestingUserEmail;
|
||||||
|
HttpsAuthority = _sharedHttpsAuthority;
|
||||||
SmtpClientFactory = _sharedSmtpClientFactory;
|
SmtpClientFactory = _sharedSmtpClientFactory;
|
||||||
Configuration = _sharedConfiguration;
|
Configuration = _sharedConfiguration;
|
||||||
SiteSettings = _sharedSiteSettings;
|
SiteSettings = _sharedSiteSettings;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Runtime.Loader;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
|
@ -37,6 +38,7 @@ public abstract class WebHostFixture : IBackendFixture
|
||||||
private static readonly object _sync = new object();
|
private static readonly object _sync = new object();
|
||||||
private static WebApplication? _app;
|
private static WebApplication? _app;
|
||||||
private static bool _isInitialized;
|
private static bool _isInitialized;
|
||||||
|
private static bool _shutdownHooksRegistered;
|
||||||
private static int _instanceCount;
|
private static int _instanceCount;
|
||||||
private static readonly List<string> _sharedAddresses = new();
|
private static readonly List<string> _sharedAddresses = new();
|
||||||
private static IServiceProvider? _sharedServices;
|
private static IServiceProvider? _sharedServices;
|
||||||
|
|
@ -63,6 +65,8 @@ public abstract class WebHostFixture : IBackendFixture
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
|
RegisterShutdownHooks();
|
||||||
|
|
||||||
if (!_isInitialized)
|
if (!_isInitialized)
|
||||||
{
|
{
|
||||||
InitializeAsync().GetAwaiter().GetResult();
|
InitializeAsync().GetAwaiter().GetResult();
|
||||||
|
|
@ -114,11 +118,19 @@ public abstract class WebHostFixture : IBackendFixture
|
||||||
/// listen port.</summary>
|
/// listen port.</summary>
|
||||||
protected virtual int HttpsPort => 5101;
|
protected virtual int HttpsPort => 5101;
|
||||||
|
|
||||||
|
/// <summary>Options used to create the WebApplicationBuilder.
|
||||||
|
/// Derived fixtures can override (for example, to set
|
||||||
|
/// ApplicationName for MVC controller discovery).</summary>
|
||||||
|
protected virtual WebApplicationOptions CreateBuilderOptions()
|
||||||
|
{
|
||||||
|
return new WebApplicationOptions();
|
||||||
|
}
|
||||||
|
|
||||||
public WebApplication App { get; private set; }
|
public WebApplication App { get; private set; }
|
||||||
|
|
||||||
private async Task InitializeAsync()
|
private async Task InitializeAsync()
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder();
|
var builder = WebApplication.CreateBuilder(CreateBuilderOptions());
|
||||||
|
|
||||||
builder.WebHost.ConfigureKestrel(options =>
|
builder.WebHost.ConfigureKestrel(options =>
|
||||||
{
|
{
|
||||||
|
|
@ -158,23 +170,40 @@ public abstract class WebHostFixture : IBackendFixture
|
||||||
_instanceCount--;
|
_instanceCount--;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsInitialized = false;
|
IsInitialized = _isInitialized;
|
||||||
|
|
||||||
if (_instanceCount > 0)
|
// Keep the shared host alive for the whole test process.
|
||||||
|
// Disposing per class/collection can race with other test
|
||||||
|
// classes and intermittently drop the listener mid-run.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterShutdownHooks()
|
||||||
|
{
|
||||||
|
if (_shutdownHooksRegistered)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppDomain.CurrentDomain.ProcessExit += (_, __) => ShutdownSharedHost();
|
||||||
|
AssemblyLoadContext.Default.Unloading += _ => ShutdownSharedHost();
|
||||||
|
_shutdownHooksRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ShutdownSharedHost()
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (!_isInitialized || _app is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isInitialized)
|
_app.StopAsync().GetAwaiter().GetResult();
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_app?.StopAsync().GetAwaiter().GetResult();
|
|
||||||
_app = null;
|
_app = null;
|
||||||
_isInitialized = false;
|
_isInitialized = false;
|
||||||
_sharedAddresses.Clear();
|
|
||||||
_sharedServices = null;
|
_sharedServices = null;
|
||||||
|
_sharedAddresses.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue