test(harness): production-fidelity DI (AddOtOpcUaRuntime) + opt-in fake driver factory

This commit is contained in:
Joseph Doherty
2026-06-18 08:55:35 -04:00
parent 21ce9e4850
commit ffb725e4c1
2 changed files with 129 additions and 1 deletions
@@ -15,6 +15,7 @@ using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
using ZB.MOM.WW.OtOpcUa.Cluster;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.ControlPlane;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Host.Health;
using ZB.MOM.WW.OtOpcUa.Runtime;
using ZB.MOM.WW.OtOpcUa.Security;
@@ -71,6 +72,12 @@ public sealed class TwoNodeClusterHarness : IAsyncDisposable
private string? _sqlDbName;
private string? _sqlConnString;
/// <summary>Optional opt-in <see cref="IDriverFactory"/> both nodes register so deployed drivers can
/// reach <c>Connected</c>/Healthy in-process. Null (default) leaves the production
/// <see cref="NullDriverFactory"/> from <c>AddOtOpcUaRuntime()</c> in place — unchanged behaviour for
/// existing tests.</summary>
private IDriverFactory? _driverFactory;
/// <summary>Gets the first web application node.</summary>
public WebApplication NodeA { get; private set; } = null!;
/// <summary>Gets the second web application node.</summary>
@@ -109,9 +116,15 @@ public sealed class TwoNodeClusterHarness : IAsyncDisposable
/// <summary>Boots both nodes and waits up to <paramref name="formationTimeout"/> for cluster convergence.</summary>
/// <param name="formationTimeout">Maximum time to wait for cluster formation; defaults to 20 seconds if not provided.</param>
public static async Task<TwoNodeClusterHarness> StartAsync(TimeSpan? formationTimeout = null)
/// <param name="driverFactory">Optional opt-in <see cref="IDriverFactory"/> both nodes register so deployed
/// drivers reach <c>Connected</c>/Healthy in-process. Null (default) leaves the production
/// <see cref="NullDriverFactory"/> default in place — unchanged behaviour for existing tests.</param>
public static async Task<TwoNodeClusterHarness> StartAsync(
TimeSpan? formationTimeout = null,
IDriverFactory? driverFactory = null)
{
var harness = new TwoNodeClusterHarness();
harness._driverFactory = driverFactory;
harness.NodeAAkkaPort = AllocateFreePort();
harness.NodeBAkkaPort = AllocateFreePort();
@@ -237,6 +250,18 @@ public sealed class TwoNodeClusterHarness : IAsyncDisposable
builder.Services.AddDbContext<OtOpcUaConfigDbContext>(opt => opt.UseInMemoryDatabase(harness.SharedDbName));
}
// Production fidelity: Program.cs calls AddOtOpcUaRuntime() (DI registration) AND
// WithOtOpcUaRuntimeActors() (Akka spawn). The harness historically called only the latter,
// so IDriverHealthPublisher fell back to NullDriverHealthPublisher (no health on the
// driver-health DPS topic) and IDriverFactory to NullDriverFactory (deployed drivers never
// reached Connected). Register the opt-in fake factory FIRST so its registration wins over the
// TryAddSingleton<IDriverFactory>(NullDriverFactory) seeded inside AddOtOpcUaRuntime; when no
// factory was passed, the Null default stays — unchanged behaviour for existing tests. Must run
// BEFORE AddAkka (see the AddOtOpcUaRuntime XML doc).
if (harness._driverFactory is not null)
builder.Services.AddSingleton(harness._driverFactory);
builder.Services.AddOtOpcUaRuntime();
builder.Services.AddOtOpcUaCluster(builder.Configuration);
builder.Services.AddAkka("otopcua", (ab, sp) =>