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); } }