using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Yavsc.Tests.Shared;
namespace Yavsc.Org.Tests;
///
/// WebApplicationFactory-based fixture for integration tests that need
/// to override services registered by the production Program.
/// Uses the in-memory so tests can hit real
/// HTTP endpoints without sockets or self-signed certificates.
///
/// Currently overrides so that
/// [Authorize("AdministratorOnly")] (and any other policy
/// requiring a role) is satisfied by sending an
/// X-Test-Role: Administrator header, without a real login.
/// Also adds which installs
/// so that User.GetUserId()
/// in user code sees a logged-in identity derived from the same
/// header.
///
/// Each instance gets its own in-memory database, identified by a
/// GUID generated in the constructor. The connection string
/// (ConnectionStrings:YavscConnection) is set as an
/// environment variable (ConnectionStrings__YavscConnection)
/// in the constructor and unset in , so the
/// production AddIdentityDBAndStores registers DbContext
/// instances against this fixture's own store. Without this, the
/// "InMemory" connection string from
/// appsettings-org.Testing.json would route every
/// instance — and any
/// running in the same process — to
/// the same backing store, leaking state between fixtures.
///
/// Env vars are used (rather than ConfigureAppConfiguration or
/// UseSetting) because WebApplicationFactory applies
/// those too late: Program.Main has already captured the
/// connection string in AddIdentityDBAndStores by the time
/// the test host's overrides take effect. Env vars are the last
/// provider added in AddConfiguration (see
/// Yavsc.Server/Helpers/ConfigHelpers.cs), so they win.
///
public class TestWebApplicationFactory : WebApplicationFactory
{
private readonly string _fixtureId = Guid.NewGuid().ToString("N");
// ASP.NET Core's environment-variable configuration provider uses
// the key ConnectionStrings__YavscConnection (double underscore
// for the section separator). Set it before the host starts so
// the per-fixture connection string wins over
// appsettings-org.Testing.json. We do NOT touch the appsettings
// file; env vars take precedence in the configuration pipeline
// (see AddConfiguration in Yavsc.Server/Helpers/ConfigHelpers.cs,
// which adds AddEnvironmentVariables last).
private static readonly object _envLock = new();
private bool _envSet;
public TestWebApplicationFactory()
{
lock (_envLock)
{
Environment.SetEnvironmentVariable(
"ConnectionStrings__YavscConnection",
InMemoryDatabaseName.For(_fixtureId));
_envSet = true;
}
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
// UseEnvironment("Testing") puts the host in a dedicated
// configuration environment so AddConfiguration("org") in
// Program.Main loads the optional appsettings-org.Testing.json
// file (which overrides the connection string and SMTP section
// for the test host). See that file for the values.
// We don't use "Development" because that environment is also
// used by the dev launcher and would change the signing
// credential path in IdentityServer; "Testing" is unambiguous.
builder.UseEnvironment("Testing");
builder.ConfigureTestServices(services =>
{
// Replace the production IAuthorizationPolicyProvider with
// the test one. The default registered by AddAuthorization
// becomes irrelevant: any GetPolicyAsync call is routed here.
services.AddSingleton();
// Register the test middleware and its startup filter.
// The startup filter wraps the production pipeline so
// TestUserMiddleware runs after UseAuthentication/Authorization.
services.AddTransient();
services.AddTransient();
});
}
protected override void Dispose(bool disposing)
{
if (disposing && _envSet)
{
lock (_envLock)
{
Environment.SetEnvironmentVariable(
"ConnectionStrings__YavscConnection", null);
_envSet = false;
}
}
base.Dispose(disposing);
}
}