Files
scadalink-design/tests/ScadaLink.IntegrationTests/ScadaLinkWebApplicationFactory.cs
Joseph Doherty 1038683c58 test(integration): repair IntegrationTests harness and stale API-key test
- ScadaLinkWebApplicationFactory removed the AkkaHostedService SINGLETON, not
  just its IHostedService registration, so IClusterNodeProvider's factory
  (Program.cs) could not resolve it — 10 tests failed at host build. Now removes
  only the factory-registered IHostedService descriptors and keeps the singleton.
- Configure an LDAP service account so ResolveUserDnAsync does search-then-bind
  against GLAuth (whose DN layout the no-service-account fallback DN never
  matched), fixing LoginEndpoint_WithValidLdapCredentials.
- IntegrationSurfaceTests: ApiKeyValidator now matches keys by HMAC hash over
  GetAllApiKeysAsync (ConfigurationDatabase-012); the test mocked the removed
  GetApiKeyByValueAsync path. Suite now 64/64.
2026-05-17 06:46:47 -04:00

107 lines
5.1 KiB
C#

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;
/// <summary>
/// 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.
/// </summary>
public class ScadaLinkWebApplicationFactory : WebApplicationFactory<Program>
{
/// <summary>
/// Environment variables that were set by this factory, to be cleaned up on dispose.
/// </summary>
private readonly Dictionary<string, string?> _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<string, string>
{
["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",
// GLAuth places users at cn=<name>,ou=<group>,ou=users,dc=... — the
// no-service-account fallback DN (uid=<name>,dc=...) does not match,
// so a service account is configured to enable search-then-bind:
// resolve the user's real DN by (uid=<name>) lookup, then bind it.
["ScadaLink__Security__LdapServiceAccountDn"] = "cn=admin,ou=SCADA-Admins,ou=users,dc=scadalink,dc=local",
["ScadaLink__Security__LdapServiceAccountPassword"] = "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<ScadaLinkDbContext>) ||
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<ScadaLinkDbContext>(options =>
options.UseInMemoryDatabase($"ScadaLink_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);
}
}
}
}