using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; namespace ScadaLink.IntegrationTests; /// /// WP-22: Startup validation — missing required config fails with clear error. /// Tests the StartupValidator that runs on boot. /// /// Note: These tests temporarily set environment variables because Program.cs reads /// configuration from env vars in the initial ConfigurationBuilder (before WebApplicationFactory /// can inject settings). Each test saves/restores env vars to avoid interference. /// public class StartupValidationTests { [Fact] public void MissingRole_ThrowsInvalidOperationException() { // Set all required config EXCEPT Role using var env = new TempEnvironment(new Dictionary { ["DOTNET_ENVIRONMENT"] = "Development", ["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", }); var factory = new WebApplicationFactory(); var ex = Assert.Throws(() => factory.CreateClient()); Assert.Contains("Role", ex.Message, StringComparison.OrdinalIgnoreCase); factory.Dispose(); } [Fact] public void MissingJwtSigningKey_ForCentral_ThrowsInvalidOperationException() { using var env = new TempEnvironment(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=x;Database=x", ["ScadaLink__Database__MachineDataDb"] = "Server=x;Database=x", ["ScadaLink__Security__LdapServer"] = "localhost", // Deliberately missing JwtSigningKey }); var factory = new WebApplicationFactory(); var ex = Assert.Throws(() => factory.CreateClient()); Assert.Contains("JwtSigningKey", ex.Message, StringComparison.OrdinalIgnoreCase); factory.Dispose(); } [Fact] public void CentralRole_StartsSuccessfully_WithValidConfig() { using var factory = new ScadaLinkWebApplicationFactory(); using var client = factory.CreateClient(); Assert.NotNull(client); } /// /// Helper to temporarily set environment variables and restore them on dispose. /// Clears all ScadaLink__ vars first to ensure a clean slate. /// private sealed class TempEnvironment : IDisposable { private readonly Dictionary _previousValues = new(); /// /// All ScadaLink env vars that might be set by other tests/factories. /// private static readonly string[] KnownKeys = { "DOTNET_ENVIRONMENT", "ScadaLink__Node__Role", "ScadaLink__Node__NodeHostname", "ScadaLink__Node__RemotingPort", "ScadaLink__Node__SiteId", "ScadaLink__Cluster__SeedNodes__0", "ScadaLink__Cluster__SeedNodes__1", "ScadaLink__Database__ConfigurationDb", "ScadaLink__Database__MachineDataDb", "ScadaLink__Database__SkipMigrations", "ScadaLink__Security__JwtSigningKey", "ScadaLink__Security__LdapServer", "ScadaLink__Security__LdapPort", "ScadaLink__Security__LdapUseTls", "ScadaLink__Security__AllowInsecureLdap", "ScadaLink__Security__LdapSearchBase", }; public TempEnvironment(Dictionary varsToSet) { // Save and clear all known keys foreach (var key in KnownKeys) { _previousValues[key] = Environment.GetEnvironmentVariable(key); Environment.SetEnvironmentVariable(key, null); } // Set the requested vars foreach (var (key, value) in varsToSet) { if (!_previousValues.ContainsKey(key)) _previousValues[key] = Environment.GetEnvironmentVariable(key); Environment.SetEnvironmentVariable(key, value); } } public void Dispose() { foreach (var (key, previousValue) in _previousValues) { Environment.SetEnvironmentVariable(key, previousValue); } } } }