using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase; using ZB.MOM.WW.ScadaBridge.Host.Actors; namespace ZB.MOM.WW.ScadaBridge.IntegrationTests; /// /// Shared WebApplicationFactory for integration tests. /// Replaces SQL Server with an in-memory database and skips migrations. /// Removes AkkaHostedService to avoid DNS resolution issues in test environments. /// Uses environment variables for config since Program.cs reads them in the initial ConfigurationBuilder /// before WebApplicationFactory can inject settings. /// public class ScadaBridgeWebApplicationFactory : WebApplicationFactory { /// /// Environment variables that were set by this factory, to be cleaned up on dispose. /// private readonly Dictionary _previousEnvVars = new(); public ScadaBridgeWebApplicationFactory() { // The initial ConfigurationBuilder in Program.cs reads env vars with AddEnvironmentVariables(). // The env var format uses __ as section separator. var envVars = new Dictionary { ["DOTNET_ENVIRONMENT"] = "Development", ["ScadaBridge__Node__Role"] = "Central", ["ScadaBridge__Node__NodeHostname"] = "localhost", ["ScadaBridge__Node__RemotingPort"] = "8081", ["ScadaBridge__Cluster__SeedNodes__0"] = "akka.tcp://scadabridge@localhost:8081", ["ScadaBridge__Cluster__SeedNodes__1"] = "akka.tcp://scadabridge@localhost:8082", ["ScadaBridge__Database__ConfigurationDb"] = "Server=localhost;Database=ScadaBridge_Test;TrustServerCertificate=True", ["ScadaBridge__Database__MachineDataDb"] = "Server=localhost;Database=ScadaBridge_MachineData_Test;TrustServerCertificate=True", ["ScadaBridge__Database__SkipMigrations"] = "true", ["ScadaBridge__Security__JwtSigningKey"] = "integration-test-signing-key-must-be-at-least-32-chars-long", // Task 1.4: LDAP settings nest under Security:Ldap (shared LdapOptions) and use // the renamed keys (Transport replaces LdapUseTls; None == plaintext for the // GLAuth dev directory, paired with AllowInsecure=true). ["ScadaBridge__Security__Ldap__Server"] = "localhost", ["ScadaBridge__Security__Ldap__Port"] = "3893", ["ScadaBridge__Security__Ldap__Transport"] = "None", ["ScadaBridge__Security__Ldap__AllowInsecure"] = "true", ["ScadaBridge__Security__Ldap__SearchBase"] = "dc=zb,dc=local", // GLAuth places users at cn=,ou=,ou=users,dc=... — a service // account is configured to enable the shared service's search-then-bind: // resolve the user's real DN by (UserNameAttribute=) lookup, then bind it. ["ScadaBridge__Security__Ldap__ServiceAccountDn"] = "cn=admin,ou=SCADA-Admins,ou=users,dc=zb,dc=local", ["ScadaBridge__Security__Ldap__ServiceAccountPassword"] = "password", }; foreach (var (key, value) in envVars) { _previousEnvVars[key] = Environment.GetEnvironmentVariable(key); Environment.SetEnvironmentVariable(key, value); } } protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("Development"); builder.ConfigureServices(services => { // Remove ALL DbContext and EF-related service registrations to avoid dual-provider conflict. // AddDbContext<> with UseSqlServer registers many internal services. We must remove them all. var descriptorsToRemove = services .Where(d => d.ServiceType == typeof(DbContextOptions) || d.ServiceType == typeof(DbContextOptions) || d.ServiceType == typeof(ScadaBridgeDbContext) || d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true) .ToList(); foreach (var d in descriptorsToRemove) services.Remove(d); // Add in-memory database as sole provider services.AddDbContext(options => options.UseInMemoryDatabase($"ScadaBridge_IntegrationTests_{Guid.NewGuid()}")); // Remove the factory-registered IHostedService registrations so // Akka.NET remoting / DNS resolution never starts in tests — but // keep the AkkaHostedService SINGLETON resolvable: IClusterNodeProvider // (and other services) depend on it via GetRequiredService. var hostedServiceDescriptors = services .Where(d => d.ServiceType == typeof(IHostedService) && d.ImplementationFactory != null) .ToList(); foreach (var d in hostedServiceDescriptors) services.Remove(d); }); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { foreach (var (key, previousValue) in _previousEnvVars) { Environment.SetEnvironmentVariable(key, previousValue); } } } }