Host infrastructure (WP-11–17): - StartupValidator with 19 validation rules - /health/ready endpoint with DB + Akka health checks - Akka.NET bootstrap via AkkaHostedService (HOCON config, cluster, remoting, SBR) - Serilog with SiteId/NodeHostname/NodeRole enrichment - DeadLetterMonitorActor with count tracking - CoordinatedShutdown wiring (no Environment.Exit) - Windows Service support (UseWindowsService) Central UI (WP-18–21): - Blazor Server shell with Bootstrap 5, role-aware NavMenu - Login/logout flow (LDAP auth → JWT → HTTP-only cookie) - CookieAuthenticationStateProvider with idle timeout - LDAP group mapping CRUD page (Admin role) - Route guards with Authorize attributes per role - SignalR reconnection overlay for failover Integration tests (WP-22): - Startup validation, auth flow, audit transactions, readiness gating 186 tests pass (1 skipped: LDAP integration), zero warnings.
101 lines
4.5 KiB
C#
101 lines
4.5 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",
|
|
};
|
|
|
|
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 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);
|
|
}
|
|
}
|
|
}
|
|
}
|