using System.Threading; using System.Threading.Tasks; using Xunit; using ZB.MOM.WW.LmxOpcUa.Host; namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers { /// /// 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(); /// 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(); } /// /// Creates fixture with FakeMxProxy + FakeGalaxyRepository (standard test data). /// The STA thread and COM interop run against FakeMxProxy. /// 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); } /// /// Creates fixture using FakeMxAccessClient directly — skips STA thread + COM entirely. /// Fastest option for tests that don't need real COM interop. /// 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; } } }