OpcUaServiceBuilder provides fluent API for constructing OpcUaService with dependency overrides (IMxProxy, IGalaxyRepository, IMxAccessClient). WithMxAccessClient skips the STA thread and COM interop entirely. OpcUaServerFixture wraps the service lifecycle with automatic port allocation (atomic counter starting at 16000), guaranteed cleanup via IAsyncLifetime, and factory methods for common test scenarios: - WithFakes() — FakeMxProxy + FakeGalaxyRepository with standard data - WithFakeMxAccessClient() — bypasses COM, fastest for most tests Also adds TestData helper with reusable hierarchy/attributes matching gr/layout.md, and 5 fixture tests verifying startup, shutdown, port isolation, and address space building. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
104 lines
3.4 KiB
C#
104 lines
3.4 KiB
C#
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Xunit;
|
|
using ZB.MOM.WW.LmxOpcUa.Host;
|
|
|
|
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;
|
|
|
|
public OpcUaService Service { get; private set; } = null!;
|
|
public int OpcUaPort { get; }
|
|
public string EndpointUrl => $"opc.tcp://localhost:{OpcUaPort}/LmxOpcUa";
|
|
|
|
private readonly OpcUaServiceBuilder _builder;
|
|
private bool _started;
|
|
|
|
public OpcUaServerFixture(OpcUaServiceBuilder builder)
|
|
{
|
|
OpcUaPort = Interlocked.Increment(ref _nextPort);
|
|
_builder = builder;
|
|
_builder.WithOpcUaPort(OpcUaPort);
|
|
_builder.DisableDashboard();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates fixture with FakeMxProxy + FakeGalaxyRepository (standard test data).
|
|
/// The STA thread and COM interop run against FakeMxProxy.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates fixture using FakeMxAccessClient directly — skips STA thread + COM entirely.
|
|
/// Fastest option for tests that don't need real COM interop.
|
|
/// </summary>
|
|
public static OpcUaServerFixture WithFakeMxAccessClient(
|
|
FakeMxAccessClient? mxClient = null,
|
|
FakeGalaxyRepository? repo = 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");
|
|
|
|
return new OpcUaServerFixture(builder);
|
|
}
|
|
|
|
public Task InitializeAsync()
|
|
{
|
|
Service = _builder.Build();
|
|
Service.Start();
|
|
_started = true;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task DisposeAsync()
|
|
{
|
|
if (_started)
|
|
{
|
|
try { Service.Stop(); }
|
|
catch { /* swallow cleanup errors */ }
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
}
|