195 lines
8.1 KiB
C#
195 lines
8.1 KiB
C#
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Xunit;
|
|
using ZB.MOM.WW.LmxOpcUa.Host;
|
|
using ZB.MOM.WW.LmxOpcUa.Host.Configuration;
|
|
using ZB.MOM.WW.LmxOpcUa.Host.Domain;
|
|
|
|
namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers
|
|
{
|
|
/// <summary>
|
|
/// xUnit fixture that manages an OpcUaService lifecycle with automatic port allocation.
|
|
/// Guarantees no port conflicts between parallel tests.
|
|
/// Usage (per-test):
|
|
/// var fixture = OpcUaServerFixture.WithFakes();
|
|
/// await fixture.InitializeAsync();
|
|
/// try { ... } finally { await fixture.DisposeAsync(); }
|
|
/// Usage (skip COM entirely):
|
|
/// var fixture = OpcUaServerFixture.WithFakeMxAccessClient();
|
|
/// </summary>
|
|
internal class OpcUaServerFixture : IAsyncLifetime
|
|
{
|
|
private static int _nextPort = 16000;
|
|
|
|
private readonly OpcUaServiceBuilder _builder;
|
|
private bool _started;
|
|
|
|
/// <summary>
|
|
/// Initializes a fixture around a prepared service builder and optional fake dependencies.
|
|
/// </summary>
|
|
/// <param name="builder">The builder used to construct the service under test.</param>
|
|
/// <param name="repo">The optional fake Galaxy repository exposed to tests.</param>
|
|
/// <param name="mxClient">The optional fake MXAccess client exposed to tests.</param>
|
|
/// <param name="mxProxy">The optional fake MXAccess proxy exposed to tests.</param>
|
|
private OpcUaServerFixture(OpcUaServiceBuilder builder,
|
|
FakeGalaxyRepository? repo = null,
|
|
FakeMxAccessClient? mxClient = null,
|
|
FakeMxProxy? mxProxy = null)
|
|
{
|
|
OpcUaPort = Interlocked.Increment(ref _nextPort);
|
|
_builder = builder;
|
|
_builder.WithOpcUaPort(OpcUaPort);
|
|
_builder.DisableDashboard();
|
|
GalaxyRepository = repo;
|
|
MxAccessClient = mxClient;
|
|
MxProxy = mxProxy;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the started service instance managed by the fixture.
|
|
/// </summary>
|
|
public OpcUaService Service { get; private set; } = null!;
|
|
|
|
/// <summary>
|
|
/// Gets the OPC UA port assigned to this fixture instance.
|
|
/// </summary>
|
|
public int OpcUaPort { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the OPC UA endpoint URL exposed by the fixture.
|
|
/// </summary>
|
|
public string EndpointUrl => $"opc.tcp://localhost:{OpcUaPort}/LmxOpcUa";
|
|
|
|
/// <summary>
|
|
/// The fake Galaxy repository injected into the service. Mutate Hierarchy/Attributes
|
|
/// then call Service.TriggerRebuild() to simulate a Galaxy redeployment.
|
|
/// </summary>
|
|
public FakeGalaxyRepository? GalaxyRepository { get; }
|
|
|
|
/// <summary>
|
|
/// The fake MxAccess client injected into the service (when using WithFakeMxAccessClient).
|
|
/// </summary>
|
|
public FakeMxAccessClient? MxAccessClient { get; }
|
|
|
|
/// <summary>
|
|
/// The fake MxProxy injected into the service (when using WithFakes).
|
|
/// </summary>
|
|
public FakeMxProxy? MxProxy { get; }
|
|
|
|
/// <summary>
|
|
/// Builds and starts the OPC UA service for the current fixture.
|
|
/// </summary>
|
|
public Task InitializeAsync()
|
|
{
|
|
Service = _builder.Build();
|
|
Service.Start();
|
|
_started = true;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the OPC UA service when the fixture had previously been started.
|
|
/// </summary>
|
|
public Task DisposeAsync()
|
|
{
|
|
if (_started)
|
|
try
|
|
{
|
|
Service.Stop();
|
|
}
|
|
catch
|
|
{
|
|
/* swallow cleanup errors */
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates fixture with FakeMxProxy + FakeGalaxyRepository (standard test data).
|
|
/// The STA thread and COM interop run against FakeMxProxy.
|
|
/// </summary>
|
|
/// <param name="proxy">An optional fake proxy to inject; otherwise a default fake is created.</param>
|
|
/// <param name="repo">An optional fake repository to inject; otherwise standard test data is used.</param>
|
|
/// <returns>A fixture configured to exercise the COM-style runtime path.</returns>
|
|
public static OpcUaServerFixture WithFakes(
|
|
FakeMxProxy? proxy = null,
|
|
FakeGalaxyRepository? repo = null)
|
|
{
|
|
var p = proxy ?? new FakeMxProxy();
|
|
var r = repo ?? new FakeGalaxyRepository
|
|
{
|
|
Hierarchy = TestData.CreateStandardHierarchy(),
|
|
Attributes = TestData.CreateStandardAttributes()
|
|
};
|
|
|
|
var builder = new OpcUaServiceBuilder()
|
|
.WithMxProxy(p)
|
|
.WithGalaxyRepository(r)
|
|
.WithGalaxyName("TestGalaxy");
|
|
|
|
return new OpcUaServerFixture(builder, r, mxProxy: p);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates fixture using FakeMxAccessClient directly — skips STA thread + COM entirely.
|
|
/// Fastest option for tests that don't need real COM interop.
|
|
/// </summary>
|
|
/// <param name="mxClient">An optional fake MXAccess client to inject; otherwise a default fake is created.</param>
|
|
/// <param name="repo">An optional fake repository to inject; otherwise standard test data is used.</param>
|
|
/// <param name="security">An optional security profile configuration for the test server.</param>
|
|
/// <param name="redundancy">An optional redundancy configuration for the test server.</param>
|
|
/// <param name="applicationUri">An optional explicit application URI for the test server.</param>
|
|
/// <param name="serverName">An optional server name override for the test server.</param>
|
|
/// <returns>A fixture configured to exercise the direct fake-client path.</returns>
|
|
public static OpcUaServerFixture WithFakeMxAccessClient(
|
|
FakeMxAccessClient? mxClient = null,
|
|
FakeGalaxyRepository? repo = null,
|
|
SecurityProfileConfiguration? security = null,
|
|
RedundancyConfiguration? redundancy = null,
|
|
string? applicationUri = null,
|
|
string? serverName = null,
|
|
AuthenticationConfiguration? authConfig = null,
|
|
IUserAuthenticationProvider? authProvider = null,
|
|
bool alarmTrackingEnabled = false,
|
|
string[]? alarmObjectFilters = null)
|
|
{
|
|
var client = mxClient ?? new FakeMxAccessClient();
|
|
var r = repo ?? new FakeGalaxyRepository
|
|
{
|
|
Hierarchy = TestData.CreateStandardHierarchy(),
|
|
Attributes = TestData.CreateStandardAttributes()
|
|
};
|
|
|
|
var builder = new OpcUaServiceBuilder()
|
|
.WithMxAccessClient(client)
|
|
.WithGalaxyRepository(r)
|
|
.WithGalaxyName("TestGalaxy");
|
|
|
|
if (security != null)
|
|
builder.WithSecurity(security);
|
|
if (redundancy != null)
|
|
builder.WithRedundancy(redundancy);
|
|
if (applicationUri != null)
|
|
builder.WithApplicationUri(applicationUri);
|
|
if (serverName != null)
|
|
builder.WithGalaxyName(serverName);
|
|
if (authConfig != null)
|
|
builder.WithAuthentication(authConfig);
|
|
if (authProvider != null)
|
|
builder.WithAuthProvider(authProvider);
|
|
if (alarmTrackingEnabled)
|
|
builder.WithAlarmTracking(true);
|
|
if (alarmObjectFilters != null)
|
|
builder.WithAlarmFilter(alarmObjectFilters);
|
|
|
|
return new OpcUaServerFixture(builder, r, client);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Exposes the node manager currently published by the running fixture so tests can assert
|
|
/// filter counters, alarm condition counts, and other runtime telemetry.
|
|
/// </summary>
|
|
public ZB.MOM.WW.LmxOpcUa.Host.OpcUa.LmxNodeManager? NodeManager => Service.NodeManagerInstance;
|
|
}
|
|
} |