using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using ScadaLink.ConfigurationDatabase; using ScadaLink.Host.Actors; namespace ScadaLink.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 ScadaLinkWebApplicationFactory : WebApplicationFactory { /// /// Environment variables that were set by this factory, to be cleaned up on dispose. /// private readonly Dictionary _previousEnvVars = new(); public ScadaLinkWebApplicationFactory() { // 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", ["ScadaLink__Node__Role"] = "Central", ["ScadaLink__Node__NodeHostname"] = "localhost", ["ScadaLink__Node__RemotingPort"] = "8081", ["ScadaLink__Cluster__SeedNodes__0"] = "akka.tcp://scadalink@localhost:8081", ["ScadaLink__Cluster__SeedNodes__1"] = "akka.tcp://scadalink@localhost:8082", ["ScadaLink__Database__ConfigurationDb"] = "Server=localhost;Database=ScadaLink_Test;TrustServerCertificate=True", ["ScadaLink__Database__MachineDataDb"] = "Server=localhost;Database=ScadaLink_MachineData_Test;TrustServerCertificate=True", ["ScadaLink__Database__SkipMigrations"] = "true", ["ScadaLink__Security__JwtSigningKey"] = "integration-test-signing-key-must-be-at-least-32-chars-long", ["ScadaLink__Security__LdapServer"] = "localhost", ["ScadaLink__Security__LdapPort"] = "3893", ["ScadaLink__Security__LdapUseTls"] = "false", ["ScadaLink__Security__AllowInsecureLdap"] = "true", ["ScadaLink__Security__LdapSearchBase"] = "dc=scadalink,dc=local", }; 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(ScadaLinkDbContext) || 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($"ScadaLink_IntegrationTests_{Guid.NewGuid()}")); // Remove AkkaHostedService to avoid Akka.NET remoting DNS resolution in tests. // It registers as both a singleton and a hosted service via factory. var akkaDescriptors = services .Where(d => d.ServiceType == typeof(AkkaHostedService) || (d.ServiceType == typeof(IHostedService) && d.ImplementationFactory != null)) .ToList(); foreach (var d in akkaDescriptors) services.Remove(d); }); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { foreach (var (key, previousValue) in _previousEnvVars) { Environment.SetEnvironmentVariable(key, previousValue); } } } }