Files
Joseph Doherty 7b0b9c7365 refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

Build green; 5 Host.Tests fail awaiting SQL login rename in next commit.
Pre-existing StaleTagMonitor timing flakes unchanged.

Rename script committed at tools/rename-to-scadabridge.sh.
2026-05-28 09:37:45 -04:00

181 lines
6.6 KiB
C#

using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ZB.MOM.WW.ScadaBridge.Host;
namespace ZB.MOM.WW.ScadaBridge.Host.Tests;
public class HostStartupTests : IDisposable
{
private readonly List<IDisposable> _disposables = new();
public void Dispose()
{
foreach (var d in _disposables)
{
try { d.Dispose(); } catch { /* best effort */ }
}
}
[Fact]
public void CentralRole_StartsWithoutError()
{
// WebApplicationFactory replays Program.Main, which reads config from files.
// Set the environment to Central so appsettings.Central.json is loaded,
// and set DOTNET_ENVIRONMENT before the factory creates the host.
var previousEnv = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
// Host-003: connection strings are externalised; supply them via env vars.
using var dbEnv = new CentralDbTestEnvironment();
try
{
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", "Central");
var factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration((_, config) =>
{
config.AddInMemoryCollection(new Dictionary<string, string?>
{
["ScadaBridge:Node:NodeHostname"] = "localhost",
["ScadaBridge:Node:RemotingPort"] = "0",
["ScadaBridge:Cluster:SeedNodes:0"] = "akka.tcp://scadabridge@localhost:2551",
["ScadaBridge:Cluster:SeedNodes:1"] = "akka.tcp://scadabridge@localhost:2552",
["ScadaBridge:Database:SkipMigrations"] = "true",
});
});
builder.UseSetting("ScadaBridge:Node:Role", "Central");
builder.UseSetting("ScadaBridge:Database:SkipMigrations", "true");
});
_disposables.Add(factory);
// Creating the server exercises the full DI container build and startup pipeline
var client = factory.CreateClient();
_disposables.Add(client);
// If we get here without exception, the central host started successfully
Assert.NotNull(client);
}
finally
{
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", previousEnv);
}
}
[Fact]
public void SiteRole_StartsWithoutError()
{
var builder = WebApplication.CreateBuilder();
builder.Configuration.Sources.Clear();
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["ScadaBridge:Node:Role"] = "Site",
["ScadaBridge:Node:NodeHostname"] = "test-site",
["ScadaBridge:Node:SiteId"] = "TestSite",
["ScadaBridge:Node:RemotingPort"] = "0",
["ScadaBridge:Node:GrpcPort"] = "0",
});
builder.Services.AddGrpc();
builder.Services.AddSingleton<ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamGrpcServer>();
SiteServiceRegistration.Configure(builder.Services, builder.Configuration);
// Remove AkkaHostedService from running
AkkaHostedServiceRemover.RemoveAkkaHostedServiceOnly(builder.Services);
var app = builder.Build();
_disposables.Add(app);
// Build succeeds = DI container is valid and all services resolve
Assert.NotNull(app);
Assert.NotNull(app.Services);
}
[Fact]
public void SiteRole_ConfiguresKestrelForGrpc()
{
var builder = WebApplication.CreateBuilder();
builder.Configuration.Sources.Clear();
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["ScadaBridge:Node:Role"] = "Site",
["ScadaBridge:Node:NodeHostname"] = "test-site",
["ScadaBridge:Node:SiteId"] = "TestSite",
["ScadaBridge:Node:RemotingPort"] = "0",
["ScadaBridge:Node:GrpcPort"] = "0",
});
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(0, listenOptions =>
{
listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http2;
});
});
builder.Services.AddGrpc();
builder.Services.AddSingleton<ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamGrpcServer>();
SiteServiceRegistration.Configure(builder.Services, builder.Configuration);
// Remove AkkaHostedService from running
AkkaHostedServiceRemover.RemoveAkkaHostedServiceOnly(builder.Services);
var app = builder.Build();
// Verify Kestrel IS configured (site now hosts gRPC via WebApplicationBuilder)
var serverType = Type.GetType(
"Microsoft.AspNetCore.Hosting.Server.IServer, Microsoft.AspNetCore.Hosting.Server.Abstractions");
if (serverType != null)
{
var server = app.Services.GetService(serverType);
Assert.NotNull(server);
}
(app as IDisposable)?.Dispose();
}
[Fact]
public void HostProject_DoesNotUseConditionalCompilation()
{
var hostProjectDir = FindHostProjectDirectory();
Assert.NotNull(hostProjectDir);
var sourceFiles = Directory.GetFiles(hostProjectDir, "*.cs", SearchOption.TopDirectoryOnly);
Assert.NotEmpty(sourceFiles);
foreach (var file in sourceFiles)
{
var content = File.ReadAllText(file);
Assert.DoesNotContain("#if", content);
Assert.DoesNotContain("#ifdef", content);
Assert.DoesNotContain("#ifndef", content);
Assert.DoesNotContain("#elif", content);
Assert.DoesNotContain("#else", content);
Assert.DoesNotContain("#endif", content);
}
}
private static string? FindHostProjectDirectory()
{
// Walk up from the test assembly location to find the src directory
var assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var dir = new DirectoryInfo(assemblyDir);
while (dir != null)
{
var hostPath = Path.Combine(dir.FullName, "src", "ZB.MOM.WW.ScadaBridge.Host");
if (Directory.Exists(hostPath))
return hostPath;
dir = dir.Parent;
}
return null;
}
}