b71fbae36e
The rig pointed at the shared 10.100.0.35 GLAuth whose serviceaccount password
was rotated (SEC-36), so central login had been failing ('Authentication service
is misconfigured') and a TEMP DisableLogin workaround was pending. Central nodes
now point at the local redundant pair (scadaproj/infra/glauth-redundant,
host.docker.internal:3893 + FallbackServers :3894), where the dev bind password
is correct — live-gated on the redeployed rig: login OK, primary-kill failover,
sticky preference (bind-count proven), walk-back on backup-kill.
AuthFlowTests factory bound as cn=admin for search-then-bind, but the current
directory grants the search capability only to serviceaccount (admin searches
return 50 Insufficient access) — stale since the GLAuth config evolved; the test
had been skipping on the closed port and failed once anything answered :3893.
Now binds as serviceaccount; AuthFlowTests 5/5 against the pair.
120 lines
6.3 KiB
C#
120 lines
6.3 KiB
C#
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;
|
|
|
|
/// <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 ScadaBridgeWebApplicationFactory : 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 ScadaBridgeWebApplicationFactory()
|
|
{
|
|
// 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",
|
|
["ScadaBridge__Node__Role"] = "Central",
|
|
// NodeName is eagerly validated at boot (arch-review 08 round 2 NF4 / plan R2-08 T7):
|
|
// an empty node name stamps the SourceNode audit column NULL, so the validator now
|
|
// fails host boot without it. The integration harness must supply one.
|
|
["ScadaBridge__Node__NodeName"] = "central-a",
|
|
["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",
|
|
// The inbound API-key pepper is a REQUIRED Central config value (StartupValidator
|
|
// enforces a >=16-char floor; it backs the peppered-HMAC verifier). Supply a fixed
|
|
// test pepper so host boot passes validation in the test environment.
|
|
["ScadaBridge__InboundApi__ApiKeyPepper"] = "integration-test-api-key-pepper-0123456789",
|
|
// 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",
|
|
// Search-then-bind needs an account holding GLAuth's `search` capability, which
|
|
// the current directory grants ONLY to `serviceaccount` (binding as `admin`
|
|
// succeeds but its searches return `50 Insufficient access`, i.e. auth reads as
|
|
// "service unavailable"). Matches the local redundant pair
|
|
// (scadaproj/infra/glauth-redundant), whose serviceaccount password is the
|
|
// well-known dev value — the shared 10.100.0.35 instance's is rotated (SEC-36).
|
|
["ScadaBridge__Security__Ldap__ServiceAccountDn"] = "cn=serviceaccount,dc=zb,dc=local",
|
|
["ScadaBridge__Security__Ldap__ServiceAccountPassword"] = "serviceaccount123",
|
|
};
|
|
|
|
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<ScadaBridgeDbContext>) ||
|
|
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<ScadaBridgeDbContext>(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);
|
|
}
|
|
}
|
|
}
|
|
}
|